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 import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY; 31 32 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE; 33 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH; 34 35 import android.R; 36 import android.annotation.CallSuper; 37 import android.annotation.CheckResult; 38 import android.annotation.ColorInt; 39 import android.annotation.DrawableRes; 40 import android.annotation.FlaggedApi; 41 import android.annotation.FloatRange; 42 import android.annotation.IntDef; 43 import android.annotation.IntRange; 44 import android.annotation.NonNull; 45 import android.annotation.Nullable; 46 import android.annotation.Px; 47 import android.annotation.RequiresPermission; 48 import android.annotation.Size; 49 import android.annotation.StringRes; 50 import android.annotation.StyleRes; 51 import android.annotation.TestApi; 52 import android.annotation.XmlRes; 53 import android.app.Activity; 54 import android.app.PendingIntent; 55 import android.app.assist.AssistStructure; 56 import android.app.compat.CompatChanges; 57 import android.compat.annotation.ChangeId; 58 import android.compat.annotation.EnabledSince; 59 import android.compat.annotation.UnsupportedAppUsage; 60 import android.content.ClipData; 61 import android.content.ClipDescription; 62 import android.content.ClipboardManager; 63 import android.content.Context; 64 import android.content.Intent; 65 import android.content.UndoManager; 66 import android.content.pm.PackageManager; 67 import android.content.res.ColorStateList; 68 import android.content.res.CompatibilityInfo; 69 import android.content.res.Configuration; 70 import android.content.res.FontScaleConverterFactory; 71 import android.content.res.Resources; 72 import android.content.res.TypedArray; 73 import android.content.res.XmlResourceParser; 74 import android.graphics.BaseCanvas; 75 import android.graphics.BlendMode; 76 import android.graphics.Canvas; 77 import android.graphics.Color; 78 import android.graphics.Insets; 79 import android.graphics.Matrix; 80 import android.graphics.Paint; 81 import android.graphics.Paint.FontMetricsInt; 82 import android.graphics.Path; 83 import android.graphics.PointF; 84 import android.graphics.PorterDuff; 85 import android.graphics.Rect; 86 import android.graphics.RectF; 87 import android.graphics.Typeface; 88 import android.graphics.drawable.Drawable; 89 import android.graphics.fonts.FontStyle; 90 import android.graphics.fonts.FontVariationAxis; 91 import android.graphics.text.LineBreakConfig; 92 import android.icu.text.DecimalFormatSymbols; 93 import android.os.AsyncTask; 94 import android.os.Build; 95 import android.os.Build.VERSION_CODES; 96 import android.os.Bundle; 97 import android.os.CancellationSignal; 98 import android.os.Handler; 99 import android.os.LocaleList; 100 import android.os.Parcel; 101 import android.os.Parcelable; 102 import android.os.ParcelableParcel; 103 import android.os.Process; 104 import android.os.SystemClock; 105 import android.os.UserHandle; 106 import android.provider.Settings; 107 import android.text.BoringLayout; 108 import android.text.ClientFlags; 109 import android.text.DynamicLayout; 110 import android.text.Editable; 111 import android.text.GetChars; 112 import android.text.GraphemeClusterSegmentFinder; 113 import android.text.GraphicsOperations; 114 import android.text.Highlights; 115 import android.text.InputFilter; 116 import android.text.InputType; 117 import android.text.Layout; 118 import android.text.NoCopySpan; 119 import android.text.ParcelableSpan; 120 import android.text.PrecomputedText; 121 import android.text.SegmentFinder; 122 import android.text.Selection; 123 import android.text.SpanWatcher; 124 import android.text.Spannable; 125 import android.text.SpannableStringBuilder; 126 import android.text.Spanned; 127 import android.text.SpannedString; 128 import android.text.StaticLayout; 129 import android.text.TextDirectionHeuristic; 130 import android.text.TextDirectionHeuristics; 131 import android.text.TextPaint; 132 import android.text.TextUtils; 133 import android.text.TextUtils.TruncateAt; 134 import android.text.TextWatcher; 135 import android.text.WordSegmentFinder; 136 import android.text.method.AllCapsTransformationMethod; 137 import android.text.method.ArrowKeyMovementMethod; 138 import android.text.method.DateKeyListener; 139 import android.text.method.DateTimeKeyListener; 140 import android.text.method.DialerKeyListener; 141 import android.text.method.DigitsKeyListener; 142 import android.text.method.KeyListener; 143 import android.text.method.LinkMovementMethod; 144 import android.text.method.MetaKeyKeyListener; 145 import android.text.method.MovementMethod; 146 import android.text.method.OffsetMapping; 147 import android.text.method.PasswordTransformationMethod; 148 import android.text.method.SingleLineTransformationMethod; 149 import android.text.method.TextKeyListener; 150 import android.text.method.TimeKeyListener; 151 import android.text.method.TransformationMethod; 152 import android.text.method.TransformationMethod2; 153 import android.text.method.WordIterator; 154 import android.text.style.CharacterStyle; 155 import android.text.style.ClickableSpan; 156 import android.text.style.ParagraphStyle; 157 import android.text.style.SpellCheckSpan; 158 import android.text.style.SuggestionSpan; 159 import android.text.style.URLSpan; 160 import android.text.style.UpdateAppearance; 161 import android.text.util.Linkify; 162 import android.util.ArraySet; 163 import android.util.AttributeSet; 164 import android.util.DisplayMetrics; 165 import android.util.IntArray; 166 import android.util.Log; 167 import android.util.SparseIntArray; 168 import android.util.TypedValue; 169 import android.view.AccessibilityIterators.TextSegmentIterator; 170 import android.view.ActionMode; 171 import android.view.Choreographer; 172 import android.view.ContentInfo; 173 import android.view.ContextMenu; 174 import android.view.DragEvent; 175 import android.view.Gravity; 176 import android.view.HapticFeedbackConstants; 177 import android.view.InputDevice; 178 import android.view.KeyCharacterMap; 179 import android.view.KeyEvent; 180 import android.view.MotionEvent; 181 import android.view.PointerIcon; 182 import android.view.View; 183 import android.view.ViewConfiguration; 184 import android.view.ViewDebug; 185 import android.view.ViewGroup; 186 import android.view.ViewGroup.LayoutParams; 187 import android.view.ViewHierarchyEncoder; 188 import android.view.ViewParent; 189 import android.view.ViewRootImpl; 190 import android.view.ViewStructure; 191 import android.view.ViewTreeObserver; 192 import android.view.accessibility.AccessibilityEvent; 193 import android.view.accessibility.AccessibilityManager; 194 import android.view.accessibility.AccessibilityNodeInfo; 195 import android.view.animation.AnimationUtils; 196 import android.view.autofill.AutofillManager; 197 import android.view.autofill.AutofillValue; 198 import android.view.contentcapture.ContentCaptureManager; 199 import android.view.contentcapture.ContentCaptureSession; 200 import android.view.inputmethod.BaseInputConnection; 201 import android.view.inputmethod.CompletionInfo; 202 import android.view.inputmethod.CorrectionInfo; 203 import android.view.inputmethod.CursorAnchorInfo; 204 import android.view.inputmethod.DeleteGesture; 205 import android.view.inputmethod.DeleteRangeGesture; 206 import android.view.inputmethod.EditorBoundsInfo; 207 import android.view.inputmethod.EditorInfo; 208 import android.view.inputmethod.ExtractedText; 209 import android.view.inputmethod.ExtractedTextRequest; 210 import android.view.inputmethod.HandwritingGesture; 211 import android.view.inputmethod.InputConnection; 212 import android.view.inputmethod.InputMethodManager; 213 import android.view.inputmethod.InsertGesture; 214 import android.view.inputmethod.InsertModeGesture; 215 import android.view.inputmethod.JoinOrSplitGesture; 216 import android.view.inputmethod.PreviewableHandwritingGesture; 217 import android.view.inputmethod.RemoveSpaceGesture; 218 import android.view.inputmethod.SelectGesture; 219 import android.view.inputmethod.SelectRangeGesture; 220 import android.view.inputmethod.TextAppearanceInfo; 221 import android.view.inputmethod.TextBoundsInfo; 222 import android.view.inspector.InspectableProperty; 223 import android.view.inspector.InspectableProperty.EnumEntry; 224 import android.view.inspector.InspectableProperty.FlagEntry; 225 import android.view.textclassifier.TextClassification; 226 import android.view.textclassifier.TextClassificationContext; 227 import android.view.textclassifier.TextClassificationManager; 228 import android.view.textclassifier.TextClassifier; 229 import android.view.textclassifier.TextLinks; 230 import android.view.textservice.SpellCheckerSubtype; 231 import android.view.textservice.TextServicesManager; 232 import android.view.translation.TranslationRequestValue; 233 import android.view.translation.TranslationSpec; 234 import android.view.translation.UiTranslationController; 235 import android.view.translation.ViewTranslationCallback; 236 import android.view.translation.ViewTranslationRequest; 237 import android.widget.RemoteViews.RemoteView; 238 239 import com.android.internal.accessibility.util.AccessibilityUtils; 240 import com.android.internal.annotations.VisibleForTesting; 241 import com.android.internal.graphics.ColorUtils; 242 import com.android.internal.inputmethod.EditableInputConnection; 243 import com.android.internal.logging.MetricsLogger; 244 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 245 import com.android.internal.util.ArrayUtils; 246 import com.android.internal.util.FastMath; 247 import com.android.internal.util.Preconditions; 248 import com.android.text.flags.Flags; 249 250 import libcore.util.EmptyArray; 251 252 import org.xmlpull.v1.XmlPullParserException; 253 254 import java.io.IOException; 255 import java.lang.annotation.Retention; 256 import java.lang.annotation.RetentionPolicy; 257 import java.lang.ref.WeakReference; 258 import java.util.ArrayList; 259 import java.util.Arrays; 260 import java.util.List; 261 import java.util.Locale; 262 import java.util.Objects; 263 import java.util.Set; 264 import java.util.concurrent.CompletableFuture; 265 import java.util.concurrent.TimeUnit; 266 import java.util.function.Consumer; 267 import java.util.function.Supplier; 268 import java.util.regex.Matcher; 269 import java.util.regex.Pattern; 270 271 /** 272 * A user interface element that displays text to the user. 273 * To provide user-editable text, see {@link EditText}. 274 * <p> 275 * The following code sample shows a typical use, with an XML layout 276 * and code to modify the contents of the text view: 277 * </p> 278 279 * <pre> 280 * <LinearLayout 281 xmlns:android="http://schemas.android.com/apk/res/android" 282 android:layout_width="match_parent" 283 android:layout_height="match_parent"> 284 * <TextView 285 * android:id="@+id/text_view_id" 286 * android:layout_height="wrap_content" 287 * android:layout_width="wrap_content" 288 * android:text="@string/hello" /> 289 * </LinearLayout> 290 * </pre> 291 * <p> 292 * This code sample demonstrates how to modify the contents of the text view 293 * defined in the previous XML layout: 294 * </p> 295 * <pre> 296 * public class MainActivity extends Activity { 297 * 298 * protected void onCreate(Bundle savedInstanceState) { 299 * super.onCreate(savedInstanceState); 300 * setContentView(R.layout.activity_main); 301 * final TextView helloTextView = (TextView) findViewById(R.id.text_view_id); 302 * helloTextView.setText(R.string.user_greeting); 303 * } 304 * } 305 * </pre> 306 * <p> 307 * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>. 308 * </p> 309 * <p> 310 * <b>XML attributes</b> 311 * <p> 312 * See {@link android.R.styleable#TextView TextView Attributes}, 313 * {@link android.R.styleable#View View Attributes} 314 * 315 * @attr ref android.R.styleable#TextView_text 316 * @attr ref android.R.styleable#TextView_bufferType 317 * @attr ref android.R.styleable#TextView_hint 318 * @attr ref android.R.styleable#TextView_textColor 319 * @attr ref android.R.styleable#TextView_textColorHighlight 320 * @attr ref android.R.styleable#TextView_textColorHint 321 * @attr ref android.R.styleable#TextView_textAppearance 322 * @attr ref android.R.styleable#TextView_textColorLink 323 * @attr ref android.R.styleable#TextView_textFontWeight 324 * @attr ref android.R.styleable#TextView_textSize 325 * @attr ref android.R.styleable#TextView_textScaleX 326 * @attr ref android.R.styleable#TextView_fontFamily 327 * @attr ref android.R.styleable#TextView_typeface 328 * @attr ref android.R.styleable#TextView_textStyle 329 * @attr ref android.R.styleable#TextView_cursorVisible 330 * @attr ref android.R.styleable#TextView_maxLines 331 * @attr ref android.R.styleable#TextView_maxHeight 332 * @attr ref android.R.styleable#TextView_lines 333 * @attr ref android.R.styleable#TextView_height 334 * @attr ref android.R.styleable#TextView_minLines 335 * @attr ref android.R.styleable#TextView_minHeight 336 * @attr ref android.R.styleable#TextView_maxEms 337 * @attr ref android.R.styleable#TextView_maxWidth 338 * @attr ref android.R.styleable#TextView_ems 339 * @attr ref android.R.styleable#TextView_width 340 * @attr ref android.R.styleable#TextView_minEms 341 * @attr ref android.R.styleable#TextView_minWidth 342 * @attr ref android.R.styleable#TextView_gravity 343 * @attr ref android.R.styleable#TextView_scrollHorizontally 344 * @attr ref android.R.styleable#TextView_password 345 * @attr ref android.R.styleable#TextView_singleLine 346 * @attr ref android.R.styleable#TextView_selectAllOnFocus 347 * @attr ref android.R.styleable#TextView_includeFontPadding 348 * @attr ref android.R.styleable#TextView_maxLength 349 * @attr ref android.R.styleable#TextView_shadowColor 350 * @attr ref android.R.styleable#TextView_shadowDx 351 * @attr ref android.R.styleable#TextView_shadowDy 352 * @attr ref android.R.styleable#TextView_shadowRadius 353 * @attr ref android.R.styleable#TextView_autoLink 354 * @attr ref android.R.styleable#TextView_linksClickable 355 * @attr ref android.R.styleable#TextView_numeric 356 * @attr ref android.R.styleable#TextView_digits 357 * @attr ref android.R.styleable#TextView_phoneNumber 358 * @attr ref android.R.styleable#TextView_inputMethod 359 * @attr ref android.R.styleable#TextView_capitalize 360 * @attr ref android.R.styleable#TextView_autoText 361 * @attr ref android.R.styleable#TextView_editable 362 * @attr ref android.R.styleable#TextView_freezesText 363 * @attr ref android.R.styleable#TextView_ellipsize 364 * @attr ref android.R.styleable#TextView_drawableTop 365 * @attr ref android.R.styleable#TextView_drawableBottom 366 * @attr ref android.R.styleable#TextView_drawableRight 367 * @attr ref android.R.styleable#TextView_drawableLeft 368 * @attr ref android.R.styleable#TextView_drawableStart 369 * @attr ref android.R.styleable#TextView_drawableEnd 370 * @attr ref android.R.styleable#TextView_drawablePadding 371 * @attr ref android.R.styleable#TextView_drawableTint 372 * @attr ref android.R.styleable#TextView_drawableTintMode 373 * @attr ref android.R.styleable#TextView_lineSpacingExtra 374 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 375 * @attr ref android.R.styleable#TextView_justificationMode 376 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 377 * @attr ref android.R.styleable#TextView_inputType 378 * @attr ref android.R.styleable#TextView_imeOptions 379 * @attr ref android.R.styleable#TextView_privateImeOptions 380 * @attr ref android.R.styleable#TextView_imeActionLabel 381 * @attr ref android.R.styleable#TextView_imeActionId 382 * @attr ref android.R.styleable#TextView_editorExtras 383 * @attr ref android.R.styleable#TextView_elegantTextHeight 384 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 385 * @attr ref android.R.styleable#TextView_letterSpacing 386 * @attr ref android.R.styleable#TextView_fontFeatureSettings 387 * @attr ref android.R.styleable#TextView_fontVariationSettings 388 * @attr ref android.R.styleable#TextView_breakStrategy 389 * @attr ref android.R.styleable#TextView_hyphenationFrequency 390 * @attr ref android.R.styleable#TextView_lineBreakStyle 391 * @attr ref android.R.styleable#TextView_lineBreakWordStyle 392 * @attr ref android.R.styleable#TextView_autoSizeTextType 393 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 394 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 395 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 396 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 397 * @attr ref android.R.styleable#TextView_textCursorDrawable 398 * @attr ref android.R.styleable#TextView_textSelectHandle 399 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 400 * @attr ref android.R.styleable#TextView_textSelectHandleRight 401 * @attr ref android.R.styleable#TextView_allowUndo 402 * @attr ref android.R.styleable#TextView_enabled 403 */ 404 @RemoteView 405 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 406 static final String LOG_TAG = "TextView"; 407 static final boolean DEBUG_EXTRACT = false; 408 static final boolean DEBUG_CURSOR = false; 409 410 private static final float[] TEMP_POSITION = new float[2]; 411 412 // Enum for the "typeface" XML parameter. 413 // TODO: How can we get this from the XML instead of hardcoding it here? 414 /** @hide */ 415 @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE}) 416 @Retention(RetentionPolicy.SOURCE) 417 public @interface XMLTypefaceAttr{} 418 private static final int DEFAULT_TYPEFACE = -1; 419 private static final int SANS = 1; 420 private static final int SERIF = 2; 421 private static final int MONOSPACE = 3; 422 423 // Enum for the "ellipsize" XML parameter. 424 private static final int ELLIPSIZE_NOT_SET = -1; 425 private static final int ELLIPSIZE_NONE = 0; 426 private static final int ELLIPSIZE_START = 1; 427 private static final int ELLIPSIZE_MIDDLE = 2; 428 private static final int ELLIPSIZE_END = 3; 429 private static final int ELLIPSIZE_MARQUEE = 4; 430 431 // Bitfield for the "numeric" XML parameter. 432 // TODO: How can we get this from the XML instead of hardcoding it here? 433 private static final int SIGNED = 2; 434 private static final int DECIMAL = 4; 435 436 /** 437 * Draw marquee text with fading edges as usual 438 */ 439 private static final int MARQUEE_FADE_NORMAL = 0; 440 441 /** 442 * Draw marquee text as ellipsize end while inactive instead of with the fade. 443 * (Useful for devices where the fade can be expensive if overdone) 444 */ 445 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1; 446 447 /** 448 * Draw marquee text with fading edges because it is currently active/animating. 449 */ 450 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2; 451 452 @UnsupportedAppUsage 453 private static final int LINES = 1; 454 private static final int EMS = LINES; 455 private static final int PIXELS = 2; 456 457 // Maximum text length for single line input. 458 private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000; 459 private InputFilter.LengthFilter mSingleLineLengthFilter = null; 460 461 private static final RectF TEMP_RECTF = new RectF(); 462 463 /** @hide */ 464 static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger 465 private static final int ANIMATED_SCROLL_GAP = 250; 466 467 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 468 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 469 470 private static final int CHANGE_WATCHER_PRIORITY = 100; 471 472 /** 473 * The span priority of the {@link OffsetMapping} that is set on the text. It must be 474 * higher than the {@link DynamicLayout}'s {@link TextWatcher}, so that the transformed text is 475 * updated before {@link DynamicLayout#reflow(CharSequence, int, int, int)} being triggered 476 * by {@link TextWatcher#onTextChanged(CharSequence, int, int, int)}. 477 */ 478 private static final int OFFSET_MAPPING_SPAN_PRIORITY = 200; 479 480 // New state used to change background based on whether this TextView is multiline. 481 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline }; 482 483 // Accessibility action to share selected text. 484 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000; 485 486 /** 487 * @hide 488 */ 489 // Accessibility action start id for "process text" actions. 490 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100; 491 492 /** Accessibility action start id for "smart" actions. @hide */ 493 static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000; 494 495 /** 496 * @hide 497 */ 498 @TestApi 499 public static final int PROCESS_TEXT_REQUEST_CODE = 100; 500 501 /** 502 * Return code of {@link #doKeyDown}. 503 */ 504 private static final int KEY_EVENT_NOT_HANDLED = 0; 505 private static final int KEY_EVENT_HANDLED = -1; 506 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1; 507 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2; 508 509 private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; 510 511 // The default value of the line break style. 512 private static final int DEFAULT_LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_NONE; 513 514 // The default value of the line break word style. 515 private static final int DEFAULT_LINE_BREAK_WORD_STYLE = 516 LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; 517 518 /** 519 * This change ID enables the fallback text line spacing (line height) for BoringLayout. 520 * @hide 521 */ 522 @ChangeId 523 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) 524 public static final long BORINGLAYOUT_FALLBACK_LINESPACING = 210923482L; // buganizer id 525 526 /** 527 * This change ID enables the fallback text line spacing (line height) for StaticLayout. 528 * @hide 529 */ 530 @ChangeId 531 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.P) 532 public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id 533 534 535 /** 536 * This change ID enables the bounding box based layout. 537 * @hide 538 */ 539 @ChangeId 540 @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM) 541 public static final long USE_BOUNDS_FOR_WIDTH = 63938206; // buganizer id 542 543 // System wide time for last cut, copy or text changed action. 544 static long sLastCutCopyOrTextChangedTime; 545 private ColorStateList mTextColor; 546 private ColorStateList mHintTextColor; 547 private ColorStateList mLinkTextColor; 548 @ViewDebug.ExportedProperty(category = "text") 549 550 /** 551 * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead. 552 */ 553 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 554 private int mCurTextColor; 555 556 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 557 private int mCurHintTextColor; 558 private boolean mFreezesText; 559 560 @UnsupportedAppUsage 561 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 562 @UnsupportedAppUsage 563 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 564 565 @UnsupportedAppUsage 566 private float mShadowRadius; 567 @UnsupportedAppUsage 568 private float mShadowDx; 569 @UnsupportedAppUsage 570 private float mShadowDy; 571 private int mShadowColor; 572 573 private int mLastOrientation; 574 575 private boolean mPreDrawRegistered; 576 private boolean mPreDrawListenerDetached; 577 578 private TextClassifier mTextClassifier; 579 private TextClassifier mTextClassificationSession; 580 private TextClassificationContext mTextClassificationContext; 581 582 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is 583 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse 584 // the view hierarchy. On the other hand, if the user is using the movement key to traverse 585 // views (i.e. the first movement was to traverse out of this view, or this view was traversed 586 // into by the user holding the movement key down) then we shouldn't prevent the focus from 587 // changing. 588 private boolean mPreventDefaultMovement; 589 590 private TextUtils.TruncateAt mEllipsize; 591 592 // A flag to indicate the cursor was hidden by IME. 593 private boolean mImeIsConsumingInput; 594 595 // Whether cursor is visible without regard to {@link mImeConsumesInput}. 596 // {@code true} is the default value. 597 private boolean mCursorVisibleFromAttr = true; 598 599 static class Drawables { 600 static final int LEFT = 0; 601 static final int TOP = 1; 602 static final int RIGHT = 2; 603 static final int BOTTOM = 3; 604 605 static final int DRAWABLE_NONE = -1; 606 static final int DRAWABLE_RIGHT = 0; 607 static final int DRAWABLE_LEFT = 1; 608 609 final Rect mCompoundRect = new Rect(); 610 611 final Drawable[] mShowing = new Drawable[4]; 612 613 ColorStateList mTintList; 614 BlendMode mBlendMode; 615 boolean mHasTint; 616 boolean mHasTintMode; 617 618 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp; 619 Drawable mDrawableLeftInitial, mDrawableRightInitial; 620 621 boolean mIsRtlCompatibilityMode; 622 boolean mOverride; 623 624 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight, 625 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp; 626 627 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight, 628 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp; 629 630 int mDrawablePadding; 631 632 int mDrawableSaved = DRAWABLE_NONE; 633 634 public Drawables(Context context) { 635 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 636 mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1 637 || !context.getApplicationInfo().hasRtlSupport(); 638 mOverride = false; 639 } 640 641 /** 642 * @return {@code true} if this object contains metadata that needs to 643 * be retained, {@code false} otherwise 644 */ 645 public boolean hasMetadata() { 646 return mDrawablePadding != 0 || mHasTintMode || mHasTint; 647 } 648 649 /** 650 * Updates the list of displayed drawables to account for the current 651 * layout direction. 652 * 653 * @param layoutDirection the current layout direction 654 * @return {@code true} if the displayed drawables changed 655 */ 656 public boolean resolveWithLayoutDirection(int layoutDirection) { 657 final Drawable previousLeft = mShowing[Drawables.LEFT]; 658 final Drawable previousRight = mShowing[Drawables.RIGHT]; 659 660 // First reset "left" and "right" drawables to their initial values 661 mShowing[Drawables.LEFT] = mDrawableLeftInitial; 662 mShowing[Drawables.RIGHT] = mDrawableRightInitial; 663 664 if (mIsRtlCompatibilityMode) { 665 // Use "start" drawable as "left" drawable if the "left" drawable was not defined 666 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) { 667 mShowing[Drawables.LEFT] = mDrawableStart; 668 mDrawableSizeLeft = mDrawableSizeStart; 669 mDrawableHeightLeft = mDrawableHeightStart; 670 } 671 // Use "end" drawable as "right" drawable if the "right" drawable was not defined 672 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) { 673 mShowing[Drawables.RIGHT] = mDrawableEnd; 674 mDrawableSizeRight = mDrawableSizeEnd; 675 mDrawableHeightRight = mDrawableHeightEnd; 676 } 677 } else { 678 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right" 679 // drawable if and only if they have been defined 680 switch(layoutDirection) { 681 case LAYOUT_DIRECTION_RTL: 682 if (mOverride) { 683 mShowing[Drawables.RIGHT] = mDrawableStart; 684 mDrawableSizeRight = mDrawableSizeStart; 685 mDrawableHeightRight = mDrawableHeightStart; 686 687 mShowing[Drawables.LEFT] = mDrawableEnd; 688 mDrawableSizeLeft = mDrawableSizeEnd; 689 mDrawableHeightLeft = mDrawableHeightEnd; 690 } 691 break; 692 693 case LAYOUT_DIRECTION_LTR: 694 default: 695 if (mOverride) { 696 mShowing[Drawables.LEFT] = mDrawableStart; 697 mDrawableSizeLeft = mDrawableSizeStart; 698 mDrawableHeightLeft = mDrawableHeightStart; 699 700 mShowing[Drawables.RIGHT] = mDrawableEnd; 701 mDrawableSizeRight = mDrawableSizeEnd; 702 mDrawableHeightRight = mDrawableHeightEnd; 703 } 704 break; 705 } 706 } 707 708 applyErrorDrawableIfNeeded(layoutDirection); 709 710 return mShowing[Drawables.LEFT] != previousLeft 711 || mShowing[Drawables.RIGHT] != previousRight; 712 } 713 714 public void setErrorDrawable(Drawable dr, TextView tv) { 715 if (mDrawableError != dr && mDrawableError != null) { 716 mDrawableError.setCallback(null); 717 } 718 mDrawableError = dr; 719 720 if (mDrawableError != null) { 721 final Rect compoundRect = mCompoundRect; 722 final int[] state = tv.getDrawableState(); 723 724 mDrawableError.setState(state); 725 mDrawableError.copyBounds(compoundRect); 726 mDrawableError.setCallback(tv); 727 mDrawableSizeError = compoundRect.width(); 728 mDrawableHeightError = compoundRect.height(); 729 } else { 730 mDrawableSizeError = mDrawableHeightError = 0; 731 } 732 } 733 734 private void applyErrorDrawableIfNeeded(int layoutDirection) { 735 // first restore the initial state if needed 736 switch (mDrawableSaved) { 737 case DRAWABLE_LEFT: 738 mShowing[Drawables.LEFT] = mDrawableTemp; 739 mDrawableSizeLeft = mDrawableSizeTemp; 740 mDrawableHeightLeft = mDrawableHeightTemp; 741 break; 742 case DRAWABLE_RIGHT: 743 mShowing[Drawables.RIGHT] = mDrawableTemp; 744 mDrawableSizeRight = mDrawableSizeTemp; 745 mDrawableHeightRight = mDrawableHeightTemp; 746 break; 747 case DRAWABLE_NONE: 748 default: 749 } 750 // then, if needed, assign the Error drawable to the correct location 751 if (mDrawableError != null) { 752 switch(layoutDirection) { 753 case LAYOUT_DIRECTION_RTL: 754 mDrawableSaved = DRAWABLE_LEFT; 755 756 mDrawableTemp = mShowing[Drawables.LEFT]; 757 mDrawableSizeTemp = mDrawableSizeLeft; 758 mDrawableHeightTemp = mDrawableHeightLeft; 759 760 mShowing[Drawables.LEFT] = mDrawableError; 761 mDrawableSizeLeft = mDrawableSizeError; 762 mDrawableHeightLeft = mDrawableHeightError; 763 break; 764 case LAYOUT_DIRECTION_LTR: 765 default: 766 mDrawableSaved = DRAWABLE_RIGHT; 767 768 mDrawableTemp = mShowing[Drawables.RIGHT]; 769 mDrawableSizeTemp = mDrawableSizeRight; 770 mDrawableHeightTemp = mDrawableHeightRight; 771 772 mShowing[Drawables.RIGHT] = mDrawableError; 773 mDrawableSizeRight = mDrawableSizeError; 774 mDrawableHeightRight = mDrawableHeightError; 775 break; 776 } 777 } 778 } 779 } 780 781 @UnsupportedAppUsage 782 Drawables mDrawables; 783 784 @UnsupportedAppUsage 785 private CharWrapper mCharWrapper; 786 787 @UnsupportedAppUsage(trackingBug = 124050217) 788 private Marquee mMarquee; 789 @UnsupportedAppUsage 790 private boolean mRestartMarquee; 791 792 private int mMarqueeRepeatLimit = 3; 793 794 private int mLastLayoutDirection = -1; 795 796 /** 797 * On some devices the fading edges add a performance penalty if used 798 * extensively in the same layout. This mode indicates how the marquee 799 * is currently being shown, if applicable. (mEllipsize will == MARQUEE) 800 */ 801 @UnsupportedAppUsage 802 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 803 804 /** 805 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores 806 * the layout that should be used when the mode switches. 807 */ 808 @UnsupportedAppUsage 809 private Layout mSavedMarqueeModeLayout; 810 811 // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal() 812 @ViewDebug.ExportedProperty(category = "text") 813 @UnsupportedAppUsage 814 private @Nullable CharSequence mText; 815 private @Nullable Spannable mSpannable; 816 private @Nullable PrecomputedText mPrecomputed; 817 818 @UnsupportedAppUsage 819 private CharSequence mTransformed; 820 @UnsupportedAppUsage 821 private BufferType mBufferType = BufferType.NORMAL; 822 823 private CharSequence mHint; 824 @UnsupportedAppUsage 825 private Layout mHintLayout; 826 private boolean mHideHint; 827 828 private MovementMethod mMovement; 829 830 private TransformationMethod mTransformation; 831 @UnsupportedAppUsage 832 private boolean mAllowTransformationLengthChange; 833 @UnsupportedAppUsage 834 private ChangeWatcher mChangeWatcher; 835 836 @UnsupportedAppUsage(trackingBug = 123769451) 837 private ArrayList<TextWatcher> mListeners; 838 839 // display attributes 840 @UnsupportedAppUsage 841 private final TextPaint mTextPaint; 842 @UnsupportedAppUsage 843 private boolean mUserSetTextScaleX; 844 @UnsupportedAppUsage 845 private Layout mLayout; 846 private boolean mLocalesChanged = false; 847 private int mTextSizeUnit = -1; 848 private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; 849 private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; 850 851 // This is used to reflect the current user preference for changing font weight and making text 852 // more bold. 853 private int mFontWeightAdjustment; 854 private Typeface mOriginalTypeface; 855 856 // True if setKeyListener() has been explicitly called 857 private boolean mListenerChanged = false; 858 // True if internationalized input should be used for numbers and date and time. 859 private final boolean mUseInternationalizedInput; 860 861 // Fallback fonts that end up getting used should be allowed to affect line spacing. 862 private static final int FALLBACK_LINE_SPACING_NONE = 0; 863 private static final int FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY = 1; 864 private static final int FALLBACK_LINE_SPACING_ALL = 2; 865 866 private int mUseFallbackLineSpacing; 867 // True if the view text can be padded for compat reasons, when the view is translated. 868 private final boolean mUseTextPaddingForUiTranslation; 869 870 private boolean mUseBoundsForWidth; 871 private boolean mShiftDrawingOffsetForStartOverhang; 872 @Nullable private Paint.FontMetrics mMinimumFontMetrics; 873 @Nullable private Paint.FontMetrics mLocalePreferredFontMetrics; 874 private boolean mUseLocalePreferredLineHeightForMinimum; 875 876 @ViewDebug.ExportedProperty(category = "text") 877 @UnsupportedAppUsage 878 private int mGravity = Gravity.TOP | Gravity.START; 879 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 880 private boolean mHorizontallyScrolling; 881 882 private int mAutoLinkMask; 883 private boolean mLinksClickable = true; 884 885 @UnsupportedAppUsage 886 private float mSpacingMult = 1.0f; 887 @UnsupportedAppUsage 888 private float mSpacingAdd = 0.0f; 889 890 /** 891 * Remembers what line height was set to originally, before we broke it down into raw pixels. 892 * 893 * <p>This is stored as a complex dimension with both value and unit packed into one field! 894 * {@see TypedValue} 895 */ 896 private int mLineHeightComplexDimen; 897 898 private int mBreakStrategy; 899 private int mHyphenationFrequency; 900 private int mJustificationMode; 901 902 @UnsupportedAppUsage 903 private int mMaximum = Integer.MAX_VALUE; 904 @UnsupportedAppUsage 905 private int mMaxMode = LINES; 906 @UnsupportedAppUsage 907 private int mMinimum = 0; 908 @UnsupportedAppUsage 909 private int mMinMode = LINES; 910 911 @UnsupportedAppUsage 912 private int mOldMaximum = mMaximum; 913 @UnsupportedAppUsage 914 private int mOldMaxMode = mMaxMode; 915 916 @UnsupportedAppUsage 917 private int mMaxWidth = Integer.MAX_VALUE; 918 @UnsupportedAppUsage 919 private int mMaxWidthMode = PIXELS; 920 @UnsupportedAppUsage 921 private int mMinWidth = 0; 922 @UnsupportedAppUsage 923 private int mMinWidthMode = PIXELS; 924 925 @UnsupportedAppUsage 926 private boolean mSingleLine; 927 @UnsupportedAppUsage 928 private int mDesiredHeightAtMeasure = -1; 929 @UnsupportedAppUsage 930 private boolean mIncludePad = true; 931 private int mDeferScroll = -1; 932 933 // tmp primitives, so we don't alloc them on each draw 934 private Rect mTempRect; 935 private long mLastScroll; 936 private Scroller mScroller; 937 private TextPaint mTempTextPaint; 938 939 private Object mTempCursor; 940 941 @UnsupportedAppUsage 942 private BoringLayout.Metrics mBoring; 943 @UnsupportedAppUsage 944 private BoringLayout.Metrics mHintBoring; 945 @UnsupportedAppUsage 946 private BoringLayout mSavedLayout; 947 @UnsupportedAppUsage 948 private BoringLayout mSavedHintLayout; 949 950 @UnsupportedAppUsage 951 private TextDirectionHeuristic mTextDir; 952 953 private InputFilter[] mFilters = NO_FILTERS; 954 955 /** 956 * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is 957 * the same as {@link Process#myUserHandle()}. 958 * 959 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 960 * other apps may need to set this so that the system can use right user's resources and 961 * services such as input methods and spell checkers.</p> 962 * 963 * @see #setTextOperationUser(UserHandle) 964 */ 965 @Nullable 966 private UserHandle mTextOperationUser; 967 968 private volatile Locale mCurrentSpellCheckerLocaleCache; 969 970 // It is possible to have a selection even when mEditor is null (programmatically set, like when 971 // a link is pressed). These highlight-related fields do not go in mEditor. 972 @UnsupportedAppUsage 973 int mHighlightColor = 0x6633B5E5; 974 private Path mHighlightPath; 975 @UnsupportedAppUsage 976 private final Paint mHighlightPaint; 977 @UnsupportedAppUsage 978 private boolean mHighlightPathBogus = true; 979 980 private List<Path> mHighlightPaths; 981 private List<Paint> mHighlightPaints; 982 private Highlights mHighlights; 983 private int[] mSearchResultHighlights = null; 984 private Paint mSearchResultHighlightPaint = null; 985 private Paint mFocusedSearchResultHighlightPaint = null; 986 private int mFocusedSearchResultHighlightColor = 0xFFFF9632; 987 private int mSearchResultHighlightColor = 0xFFFFFF00; 988 989 private int mFocusedSearchResultIndex = -1; 990 private int mGesturePreviewHighlightStart = -1; 991 private int mGesturePreviewHighlightEnd = -1; 992 private Paint mGesturePreviewHighlightPaint; 993 private final List<Path> mPathRecyclePool = new ArrayList<>(); 994 private boolean mHighlightPathsBogus = true; 995 996 // Although these fields are specific to editable text, they are not added to Editor because 997 // they are defined by the TextView's style and are theme-dependent. 998 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 999 int mCursorDrawableRes; 1000 private Drawable mCursorDrawable; 1001 // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code 1002 // by removing it, but we would break apps targeting <= P that use it by reflection. 1003 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 1004 int mTextSelectHandleLeftRes; 1005 private Drawable mTextSelectHandleLeft; 1006 // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code 1007 // by removing it, but we would break apps targeting <= P that use it by reflection. 1008 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 1009 int mTextSelectHandleRightRes; 1010 private Drawable mTextSelectHandleRight; 1011 // Note: this might be stale if setTextSelectHandle is used. We could simplify the code 1012 // by removing it, but we would break apps targeting <= P that use it by reflection. 1013 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 1014 int mTextSelectHandleRes; 1015 private Drawable mTextSelectHandle; 1016 int mTextEditSuggestionItemLayout; 1017 int mTextEditSuggestionContainerLayout; 1018 int mTextEditSuggestionHighlightStyle; 1019 1020 private static final int NO_POINTER_ID = -1; 1021 /** 1022 * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among 1023 * TextView and the handle views which are rendered on popup windows. 1024 */ 1025 private int mPrimePointerId = NO_POINTER_ID; 1026 1027 /** 1028 * Whether the prime pointer is from the event delivered to selection handle or insertion 1029 * handle. 1030 */ 1031 private boolean mIsPrimePointerFromHandleView; 1032 1033 /** 1034 * {@link EditText} specific data, created on demand when one of the Editor fields is used. 1035 * See {@link #createEditorIfNeeded()}. 1036 */ 1037 @UnsupportedAppUsage 1038 private Editor mEditor; 1039 1040 private static final int DEVICE_PROVISIONED_UNKNOWN = 0; 1041 private static final int DEVICE_PROVISIONED_NO = 1; 1042 private static final int DEVICE_PROVISIONED_YES = 2; 1043 1044 /** 1045 * Some special options such as sharing selected text should only be shown if the device 1046 * is provisioned. Only check the provisioned state once for a given view instance. 1047 */ 1048 private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN; 1049 1050 /** 1051 * The last input source on this TextView. 1052 * 1053 * Use the SOURCE_TOUCHSCREEN as the default value for backward compatibility. There could be a 1054 * non UI event originated ActionMode initiation, e.g. API call, a11y events, etc. 1055 */ 1056 private int mLastInputSource = InputDevice.SOURCE_TOUCHSCREEN; 1057 1058 /** 1059 * The TextView does not auto-size text (default). 1060 */ 1061 public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; 1062 1063 /** 1064 * The TextView scales text size both horizontally and vertically to fit within the 1065 * container. 1066 */ 1067 public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; 1068 1069 /** @hide */ 1070 @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = { 1071 AUTO_SIZE_TEXT_TYPE_NONE, 1072 AUTO_SIZE_TEXT_TYPE_UNIFORM 1073 }) 1074 @Retention(RetentionPolicy.SOURCE) 1075 public @interface AutoSizeTextType {} 1076 // Default minimum size for auto-sizing text in scaled pixels. 1077 private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12; 1078 // Default maximum size for auto-sizing text in scaled pixels. 1079 private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112; 1080 // Default value for the step size in pixels. 1081 private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1; 1082 // Use this to specify that any of the auto-size configuration int values have not been set. 1083 private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f; 1084 // Auto-size text type. 1085 private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 1086 // Specify if auto-size text is needed. 1087 private boolean mNeedsAutoSizeText = false; 1088 // Step size for auto-sizing in pixels. 1089 private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1090 // Minimum text size for auto-sizing in pixels. 1091 private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1092 // Maximum text size for auto-sizing in pixels. 1093 private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1094 // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from 1095 // when auto-sizing text. 1096 private int[] mAutoSizeTextSizesInPx = EmptyArray.INT; 1097 // Specifies whether auto-size should use the provided auto size steps set or if it should 1098 // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and 1099 // mAutoSizeStepGranularityInPx. 1100 private boolean mHasPresetAutoSizeValues = false; 1101 1102 // Autofill-related attributes 1103 // 1104 // Indicates whether the text was set statically or dynamically, so it can be used to 1105 // sanitize autofill requests. 1106 private boolean mTextSetFromXmlOrResourceId = false; 1107 // Resource id used to set the text. 1108 private @StringRes int mTextId = Resources.ID_NULL; 1109 // Resource id used to set the hint. 1110 private @StringRes int mHintId = Resources.ID_NULL; 1111 // 1112 // End of autofill-related attributes 1113 1114 private Pattern mWhitespacePattern; 1115 1116 /** 1117 * Kick-start the font cache for the zygote process (to pay the cost of 1118 * initializing freetype for our default font only once). 1119 * @hide 1120 */ 1121 public static void preloadFontCache() { 1122 if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) { 1123 return; 1124 } 1125 Paint p = new Paint(); 1126 p.setAntiAlias(true); 1127 // Ensure that the Typeface is loaded here. 1128 // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto. 1129 // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here 1130 // since Paint.measureText can not be called without Typeface static initializer. 1131 p.setTypeface(Typeface.DEFAULT); 1132 // We don't care about the result, just the side-effect of measuring. 1133 p.measureText("H"); 1134 } 1135 1136 /** 1137 * Interface definition for a callback to be invoked when an action is 1138 * performed on the editor. 1139 */ 1140 public interface OnEditorActionListener { 1141 /** 1142 * Called when an action is being performed. 1143 * 1144 * @param v The view that was clicked. 1145 * @param actionId Identifier of the action. This will be either the 1146 * identifier you supplied, or {@link EditorInfo#IME_NULL 1147 * EditorInfo.IME_NULL} if being called due to the enter key 1148 * being pressed. Starting from Android 14, the action identifier will 1149 * also be included when triggered by an enter key if the input is 1150 * constrained to a single line. 1151 * @param event If triggered by an enter key, this is the event; 1152 * otherwise, this is null. 1153 * @return Return true if you have consumed the action, else false. 1154 */ 1155 boolean onEditorAction(TextView v, int actionId, KeyEvent event); 1156 } 1157 1158 public TextView(Context context) { 1159 this(context, null); 1160 } 1161 1162 public TextView(Context context, @Nullable AttributeSet attrs) { 1163 this(context, attrs, com.android.internal.R.attr.textViewStyle); 1164 } 1165 1166 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 1167 this(context, attrs, defStyleAttr, 0); 1168 } 1169 1170 @SuppressWarnings("deprecation") 1171 public TextView( 1172 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 1173 super(context, attrs, defStyleAttr, defStyleRes); 1174 1175 // TextView is important by default, unless app developer overrode attribute. 1176 if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { 1177 setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); 1178 } 1179 if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) { 1180 setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES); 1181 } 1182 1183 setTextInternal(""); 1184 1185 final Resources res = getResources(); 1186 final CompatibilityInfo compat = res.getCompatibilityInfo(); 1187 1188 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 1189 mTextPaint.density = res.getDisplayMetrics().density; 1190 mTextPaint.setCompatibilityScaling(compat.applicationScale); 1191 1192 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1193 mHighlightPaint.setCompatibilityScaling(compat.applicationScale); 1194 1195 mMovement = getDefaultMovementMethod(); 1196 1197 mTransformation = null; 1198 1199 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 1200 attributes.mTextColor = ColorStateList.valueOf(0xFF000000); 1201 attributes.mTextSize = 15; 1202 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; 1203 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; 1204 mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; 1205 mLastOrientation = getResources().getConfiguration().orientation; 1206 1207 final Resources.Theme theme = context.getTheme(); 1208 1209 /* 1210 * Look the appearance up without checking first if it exists because 1211 * almost every TextView has one and it greatly simplifies the logic 1212 * to be able to parse the appearance first and then let specific tags 1213 * for this View override it. 1214 */ 1215 TypedArray a = theme.obtainStyledAttributes(attrs, 1216 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); 1217 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance, 1218 attrs, a, defStyleAttr, defStyleRes); 1219 TypedArray appearance = null; 1220 int ap = a.getResourceId( 1221 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); 1222 a.recycle(); 1223 if (ap != -1) { 1224 appearance = theme.obtainStyledAttributes( 1225 ap, com.android.internal.R.styleable.TextAppearance); 1226 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance, 1227 null, appearance, 0, ap); 1228 } 1229 if (appearance != null) { 1230 readTextAppearance(context, appearance, attributes, false /* styleArray */); 1231 attributes.mFontFamilyExplicit = false; 1232 appearance.recycle(); 1233 } 1234 1235 boolean editable = getDefaultEditable(); 1236 CharSequence inputMethod = null; 1237 int numeric = 0; 1238 CharSequence digits = null; 1239 boolean phone = false; 1240 boolean autotext = false; 1241 int autocap = -1; 1242 int buffertype = 0; 1243 boolean selectallonfocus = false; 1244 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 1245 drawableBottom = null, drawableStart = null, drawableEnd = null; 1246 ColorStateList drawableTint = null; 1247 BlendMode drawableTintMode = null; 1248 int drawablePadding = 0; 1249 int ellipsize = ELLIPSIZE_NOT_SET; 1250 boolean singleLine = false; 1251 int maxlength = -1; 1252 CharSequence text = ""; 1253 CharSequence hint = null; 1254 boolean password = false; 1255 float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1256 float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1257 float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1258 int inputType = EditorInfo.TYPE_NULL; 1259 a = theme.obtainStyledAttributes( 1260 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); 1261 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a, 1262 defStyleAttr, defStyleRes); 1263 int firstBaselineToTopHeight = -1; 1264 int lastBaselineToBottomHeight = -1; 1265 float lineHeight = -1f; 1266 int lineHeightUnit = -1; 1267 boolean hasUseBoundForWidthValue = false; 1268 1269 readTextAppearance(context, a, attributes, true /* styleArray */); 1270 1271 int n = a.getIndexCount(); 1272 1273 // Must set id in a temporary variable because it will be reset by setText() 1274 boolean textIsSetFromXml = false; 1275 for (int i = 0; i < n; i++) { 1276 int attr = a.getIndex(i); 1277 1278 switch (attr) { 1279 case com.android.internal.R.styleable.TextView_editable: 1280 editable = a.getBoolean(attr, editable); 1281 break; 1282 1283 case com.android.internal.R.styleable.TextView_inputMethod: 1284 inputMethod = a.getText(attr); 1285 break; 1286 1287 case com.android.internal.R.styleable.TextView_numeric: 1288 numeric = a.getInt(attr, numeric); 1289 break; 1290 1291 case com.android.internal.R.styleable.TextView_digits: 1292 digits = a.getText(attr); 1293 break; 1294 1295 case com.android.internal.R.styleable.TextView_phoneNumber: 1296 phone = a.getBoolean(attr, phone); 1297 break; 1298 1299 case com.android.internal.R.styleable.TextView_autoText: 1300 autotext = a.getBoolean(attr, autotext); 1301 break; 1302 1303 case com.android.internal.R.styleable.TextView_capitalize: 1304 autocap = a.getInt(attr, autocap); 1305 break; 1306 1307 case com.android.internal.R.styleable.TextView_bufferType: 1308 buffertype = a.getInt(attr, buffertype); 1309 break; 1310 1311 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 1312 selectallonfocus = a.getBoolean(attr, selectallonfocus); 1313 break; 1314 1315 case com.android.internal.R.styleable.TextView_autoLink: 1316 mAutoLinkMask = a.getInt(attr, 0); 1317 break; 1318 1319 case com.android.internal.R.styleable.TextView_linksClickable: 1320 mLinksClickable = a.getBoolean(attr, true); 1321 break; 1322 1323 case com.android.internal.R.styleable.TextView_drawableLeft: 1324 drawableLeft = a.getDrawable(attr); 1325 break; 1326 1327 case com.android.internal.R.styleable.TextView_drawableTop: 1328 drawableTop = a.getDrawable(attr); 1329 break; 1330 1331 case com.android.internal.R.styleable.TextView_drawableRight: 1332 drawableRight = a.getDrawable(attr); 1333 break; 1334 1335 case com.android.internal.R.styleable.TextView_drawableBottom: 1336 drawableBottom = a.getDrawable(attr); 1337 break; 1338 1339 case com.android.internal.R.styleable.TextView_drawableStart: 1340 drawableStart = a.getDrawable(attr); 1341 break; 1342 1343 case com.android.internal.R.styleable.TextView_drawableEnd: 1344 drawableEnd = a.getDrawable(attr); 1345 break; 1346 1347 case com.android.internal.R.styleable.TextView_drawableTint: 1348 drawableTint = a.getColorStateList(attr); 1349 break; 1350 1351 case com.android.internal.R.styleable.TextView_drawableTintMode: 1352 drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1), 1353 drawableTintMode); 1354 break; 1355 1356 case com.android.internal.R.styleable.TextView_drawablePadding: 1357 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 1358 break; 1359 1360 case com.android.internal.R.styleable.TextView_maxLines: 1361 setMaxLines(a.getInt(attr, -1)); 1362 break; 1363 1364 case com.android.internal.R.styleable.TextView_maxHeight: 1365 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 1366 break; 1367 1368 case com.android.internal.R.styleable.TextView_lines: 1369 setLines(a.getInt(attr, -1)); 1370 break; 1371 1372 case com.android.internal.R.styleable.TextView_height: 1373 setHeight(a.getDimensionPixelSize(attr, -1)); 1374 break; 1375 1376 case com.android.internal.R.styleable.TextView_minLines: 1377 setMinLines(a.getInt(attr, -1)); 1378 break; 1379 1380 case com.android.internal.R.styleable.TextView_minHeight: 1381 setMinHeight(a.getDimensionPixelSize(attr, -1)); 1382 break; 1383 1384 case com.android.internal.R.styleable.TextView_maxEms: 1385 setMaxEms(a.getInt(attr, -1)); 1386 break; 1387 1388 case com.android.internal.R.styleable.TextView_maxWidth: 1389 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 1390 break; 1391 1392 case com.android.internal.R.styleable.TextView_ems: 1393 setEms(a.getInt(attr, -1)); 1394 break; 1395 1396 case com.android.internal.R.styleable.TextView_width: 1397 setWidth(a.getDimensionPixelSize(attr, -1)); 1398 break; 1399 1400 case com.android.internal.R.styleable.TextView_minEms: 1401 setMinEms(a.getInt(attr, -1)); 1402 break; 1403 1404 case com.android.internal.R.styleable.TextView_minWidth: 1405 setMinWidth(a.getDimensionPixelSize(attr, -1)); 1406 break; 1407 1408 case com.android.internal.R.styleable.TextView_gravity: 1409 setGravity(a.getInt(attr, -1)); 1410 break; 1411 1412 case com.android.internal.R.styleable.TextView_hint: 1413 mHintId = a.getResourceId(attr, Resources.ID_NULL); 1414 hint = a.getText(attr); 1415 break; 1416 1417 case com.android.internal.R.styleable.TextView_text: 1418 textIsSetFromXml = true; 1419 mTextId = a.getResourceId(attr, Resources.ID_NULL); 1420 text = a.getText(attr); 1421 break; 1422 1423 case com.android.internal.R.styleable.TextView_scrollHorizontally: 1424 if (a.getBoolean(attr, false)) { 1425 setHorizontallyScrolling(true); 1426 } 1427 break; 1428 1429 case com.android.internal.R.styleable.TextView_singleLine: 1430 singleLine = a.getBoolean(attr, singleLine); 1431 break; 1432 1433 case com.android.internal.R.styleable.TextView_ellipsize: 1434 ellipsize = a.getInt(attr, ellipsize); 1435 break; 1436 1437 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 1438 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 1439 break; 1440 1441 case com.android.internal.R.styleable.TextView_includeFontPadding: 1442 if (!a.getBoolean(attr, true)) { 1443 setIncludeFontPadding(false); 1444 } 1445 break; 1446 1447 case com.android.internal.R.styleable.TextView_cursorVisible: 1448 if (!a.getBoolean(attr, true)) { 1449 setCursorVisible(false); 1450 } 1451 break; 1452 1453 case com.android.internal.R.styleable.TextView_maxLength: 1454 maxlength = a.getInt(attr, -1); 1455 break; 1456 1457 case com.android.internal.R.styleable.TextView_textScaleX: 1458 setTextScaleX(a.getFloat(attr, 1.0f)); 1459 break; 1460 1461 case com.android.internal.R.styleable.TextView_freezesText: 1462 mFreezesText = a.getBoolean(attr, false); 1463 break; 1464 1465 case com.android.internal.R.styleable.TextView_enabled: 1466 setEnabled(a.getBoolean(attr, isEnabled())); 1467 break; 1468 1469 case com.android.internal.R.styleable.TextView_password: 1470 password = a.getBoolean(attr, password); 1471 break; 1472 1473 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 1474 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 1475 break; 1476 1477 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 1478 mSpacingMult = a.getFloat(attr, mSpacingMult); 1479 break; 1480 1481 case com.android.internal.R.styleable.TextView_inputType: 1482 inputType = a.getInt(attr, EditorInfo.TYPE_NULL); 1483 break; 1484 1485 case com.android.internal.R.styleable.TextView_allowUndo: 1486 createEditorIfNeeded(); 1487 mEditor.mAllowUndo = a.getBoolean(attr, true); 1488 break; 1489 1490 case com.android.internal.R.styleable.TextView_imeOptions: 1491 createEditorIfNeeded(); 1492 mEditor.createInputContentTypeIfNeeded(); 1493 mEditor.mInputContentType.imeOptions = a.getInt(attr, 1494 mEditor.mInputContentType.imeOptions); 1495 break; 1496 1497 case com.android.internal.R.styleable.TextView_imeActionLabel: 1498 createEditorIfNeeded(); 1499 mEditor.createInputContentTypeIfNeeded(); 1500 mEditor.mInputContentType.imeActionLabel = a.getText(attr); 1501 break; 1502 1503 case com.android.internal.R.styleable.TextView_imeActionId: 1504 createEditorIfNeeded(); 1505 mEditor.createInputContentTypeIfNeeded(); 1506 mEditor.mInputContentType.imeActionId = a.getInt(attr, 1507 mEditor.mInputContentType.imeActionId); 1508 break; 1509 1510 case com.android.internal.R.styleable.TextView_privateImeOptions: 1511 setPrivateImeOptions(a.getString(attr)); 1512 break; 1513 1514 case com.android.internal.R.styleable.TextView_editorExtras: 1515 try { 1516 setInputExtras(a.getResourceId(attr, 0)); 1517 } catch (XmlPullParserException e) { 1518 Log.w(LOG_TAG, "Failure reading input extras", e); 1519 } catch (IOException e) { 1520 Log.w(LOG_TAG, "Failure reading input extras", e); 1521 } 1522 break; 1523 1524 case com.android.internal.R.styleable.TextView_textCursorDrawable: 1525 mCursorDrawableRes = a.getResourceId(attr, 0); 1526 break; 1527 1528 case com.android.internal.R.styleable.TextView_textSelectHandleLeft: 1529 mTextSelectHandleLeftRes = a.getResourceId(attr, 0); 1530 break; 1531 1532 case com.android.internal.R.styleable.TextView_textSelectHandleRight: 1533 mTextSelectHandleRightRes = a.getResourceId(attr, 0); 1534 break; 1535 1536 case com.android.internal.R.styleable.TextView_textSelectHandle: 1537 mTextSelectHandleRes = a.getResourceId(attr, 0); 1538 break; 1539 1540 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout: 1541 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0); 1542 break; 1543 1544 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout: 1545 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0); 1546 break; 1547 1548 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle: 1549 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0); 1550 break; 1551 1552 case com.android.internal.R.styleable.TextView_textIsSelectable: 1553 setTextIsSelectable(a.getBoolean(attr, false)); 1554 break; 1555 1556 case com.android.internal.R.styleable.TextView_breakStrategy: 1557 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); 1558 break; 1559 1560 case com.android.internal.R.styleable.TextView_hyphenationFrequency: 1561 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); 1562 break; 1563 1564 case com.android.internal.R.styleable.TextView_lineBreakStyle: 1565 mLineBreakStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE); 1566 break; 1567 1568 case com.android.internal.R.styleable.TextView_lineBreakWordStyle: 1569 mLineBreakWordStyle = a.getInt(attr, 1570 LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE); 1571 break; 1572 1573 case com.android.internal.R.styleable.TextView_autoSizeTextType: 1574 mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); 1575 break; 1576 1577 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity: 1578 autoSizeStepGranularityInPx = a.getDimension(attr, 1579 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1580 break; 1581 1582 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize: 1583 autoSizeMinTextSizeInPx = a.getDimension(attr, 1584 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1585 break; 1586 1587 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize: 1588 autoSizeMaxTextSizeInPx = a.getDimension(attr, 1589 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1590 break; 1591 1592 case com.android.internal.R.styleable.TextView_autoSizePresetSizes: 1593 final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0); 1594 if (autoSizeStepSizeArrayResId > 0) { 1595 final TypedArray autoSizePresetTextSizes = a.getResources() 1596 .obtainTypedArray(autoSizeStepSizeArrayResId); 1597 setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes); 1598 autoSizePresetTextSizes.recycle(); 1599 } 1600 break; 1601 case com.android.internal.R.styleable.TextView_justificationMode: 1602 mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE); 1603 break; 1604 1605 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight: 1606 firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1); 1607 break; 1608 1609 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight: 1610 lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1); 1611 break; 1612 1613 case com.android.internal.R.styleable.TextView_lineHeight: 1614 TypedValue peekValue = a.peekValue(attr); 1615 if (peekValue != null && peekValue.type == TypedValue.TYPE_DIMENSION) { 1616 lineHeightUnit = peekValue.getComplexUnit(); 1617 lineHeight = TypedValue.complexToFloat(peekValue.data); 1618 } else { 1619 lineHeight = a.getDimensionPixelSize(attr, -1); 1620 } 1621 break; 1622 case com.android.internal.R.styleable.TextView_useBoundsForWidth: 1623 mUseBoundsForWidth = a.getBoolean(attr, false); 1624 hasUseBoundForWidthValue = true; 1625 break; 1626 case com.android.internal.R.styleable 1627 .TextView_shiftDrawingOffsetForStartOverhang: 1628 mShiftDrawingOffsetForStartOverhang = a.getBoolean(attr, false); 1629 break; 1630 case com.android.internal.R.styleable 1631 .TextView_useLocalePreferredLineHeightForMinimum: 1632 mUseLocalePreferredLineHeightForMinimum = a.getBoolean(attr, false); 1633 break; 1634 } 1635 } 1636 1637 a.recycle(); 1638 1639 BufferType bufferType = BufferType.EDITABLE; 1640 1641 final int variation = 1642 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 1643 final boolean passwordInputType = variation 1644 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); 1645 final boolean webPasswordInputType = variation 1646 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD); 1647 final boolean numberPasswordInputType = variation 1648 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 1649 1650 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 1651 mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; 1652 if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) { 1653 mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_ALL; 1654 } else if (CompatChanges.isChangeEnabled(STATICLAYOUT_FALLBACK_LINESPACING)) { 1655 mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; 1656 } else { 1657 mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE; 1658 } 1659 1660 if (!hasUseBoundForWidthValue) { 1661 if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) { 1662 mUseBoundsForWidth = ClientFlags.useBoundsForWidth(); 1663 } else { 1664 mUseBoundsForWidth = false; 1665 } 1666 } 1667 1668 // TODO(b/179693024): Use a ChangeId instead. 1669 mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R; 1670 1671 if (inputMethod != null) { 1672 Class<?> c; 1673 1674 try { 1675 c = Class.forName(inputMethod.toString()); 1676 } catch (ClassNotFoundException ex) { 1677 throw new RuntimeException(ex); 1678 } 1679 1680 try { 1681 createEditorIfNeeded(); 1682 mEditor.mKeyListener = (KeyListener) c.newInstance(); 1683 } catch (InstantiationException ex) { 1684 throw new RuntimeException(ex); 1685 } catch (IllegalAccessException ex) { 1686 throw new RuntimeException(ex); 1687 } 1688 try { 1689 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1690 ? inputType 1691 : mEditor.mKeyListener.getInputType(); 1692 } catch (IncompatibleClassChangeError e) { 1693 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1694 } 1695 } else if (digits != null) { 1696 createEditorIfNeeded(); 1697 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString()); 1698 // If no input type was specified, we will default to generic 1699 // text, since we can't tell the IME about the set of digits 1700 // that was selected. 1701 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1702 ? inputType : EditorInfo.TYPE_CLASS_TEXT; 1703 } else if (inputType != EditorInfo.TYPE_NULL) { 1704 setInputType(inputType, true); 1705 // If set, the input type overrides what was set using the deprecated singleLine flag. 1706 singleLine = !isMultilineInputType(inputType); 1707 } else if (phone) { 1708 createEditorIfNeeded(); 1709 mEditor.mKeyListener = DialerKeyListener.getInstance(); 1710 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; 1711 } else if (numeric != 0) { 1712 createEditorIfNeeded(); 1713 mEditor.mKeyListener = DigitsKeyListener.getInstance( 1714 null, // locale 1715 (numeric & SIGNED) != 0, 1716 (numeric & DECIMAL) != 0); 1717 inputType = mEditor.mKeyListener.getInputType(); 1718 mEditor.mInputType = inputType; 1719 } else if (autotext || autocap != -1) { 1720 TextKeyListener.Capitalize cap; 1721 1722 inputType = EditorInfo.TYPE_CLASS_TEXT; 1723 1724 switch (autocap) { 1725 case 1: 1726 cap = TextKeyListener.Capitalize.SENTENCES; 1727 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 1728 break; 1729 1730 case 2: 1731 cap = TextKeyListener.Capitalize.WORDS; 1732 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 1733 break; 1734 1735 case 3: 1736 cap = TextKeyListener.Capitalize.CHARACTERS; 1737 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 1738 break; 1739 1740 default: 1741 cap = TextKeyListener.Capitalize.NONE; 1742 break; 1743 } 1744 1745 createEditorIfNeeded(); 1746 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap); 1747 mEditor.mInputType = inputType; 1748 } else if (editable) { 1749 createEditorIfNeeded(); 1750 mEditor.mKeyListener = TextKeyListener.getInstance(); 1751 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1752 } else if (isTextSelectable()) { 1753 // Prevent text changes from keyboard. 1754 if (mEditor != null) { 1755 mEditor.mKeyListener = null; 1756 mEditor.mInputType = EditorInfo.TYPE_NULL; 1757 } 1758 bufferType = BufferType.SPANNABLE; 1759 // So that selection can be changed using arrow keys and touch is handled. 1760 setMovementMethod(ArrowKeyMovementMethod.getInstance()); 1761 } else { 1762 if (mEditor != null) mEditor.mKeyListener = null; 1763 1764 switch (buffertype) { 1765 case 0: 1766 bufferType = BufferType.NORMAL; 1767 break; 1768 case 1: 1769 bufferType = BufferType.SPANNABLE; 1770 break; 1771 case 2: 1772 bufferType = BufferType.EDITABLE; 1773 break; 1774 } 1775 } 1776 1777 if (mEditor != null) { 1778 mEditor.adjustInputType(password, passwordInputType, webPasswordInputType, 1779 numberPasswordInputType); 1780 } 1781 1782 if (selectallonfocus) { 1783 createEditorIfNeeded(); 1784 mEditor.mSelectAllOnFocus = true; 1785 1786 if (bufferType == BufferType.NORMAL) { 1787 bufferType = BufferType.SPANNABLE; 1788 } 1789 } 1790 1791 // Set up the tint (if needed) before setting the drawables so that it 1792 // gets applied correctly. 1793 if (drawableTint != null || drawableTintMode != null) { 1794 if (mDrawables == null) { 1795 mDrawables = new Drawables(context); 1796 } 1797 if (drawableTint != null) { 1798 mDrawables.mTintList = drawableTint; 1799 mDrawables.mHasTint = true; 1800 } 1801 if (drawableTintMode != null) { 1802 mDrawables.mBlendMode = drawableTintMode; 1803 mDrawables.mHasTintMode = true; 1804 } 1805 } 1806 1807 // This call will save the initial left/right drawables 1808 setCompoundDrawablesWithIntrinsicBounds( 1809 drawableLeft, drawableTop, drawableRight, drawableBottom); 1810 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd); 1811 setCompoundDrawablePadding(drawablePadding); 1812 1813 // Same as setSingleLine(), but make sure the transformation method and the maximum number 1814 // of lines of height are unchanged for multi-line TextViews. 1815 setInputTypeSingleLine(singleLine); 1816 applySingleLine(singleLine, singleLine, singleLine, 1817 // Does not apply automated max length filter since length filter will be resolved 1818 // later in this function. 1819 false 1820 ); 1821 1822 if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) { 1823 ellipsize = ELLIPSIZE_END; 1824 } 1825 1826 switch (ellipsize) { 1827 case ELLIPSIZE_START: 1828 setEllipsize(TextUtils.TruncateAt.START); 1829 break; 1830 case ELLIPSIZE_MIDDLE: 1831 setEllipsize(TextUtils.TruncateAt.MIDDLE); 1832 break; 1833 case ELLIPSIZE_END: 1834 setEllipsize(TextUtils.TruncateAt.END); 1835 break; 1836 case ELLIPSIZE_MARQUEE: 1837 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) { 1838 setHorizontalFadingEdgeEnabled(true); 1839 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 1840 } else { 1841 setHorizontalFadingEdgeEnabled(false); 1842 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 1843 } 1844 setEllipsize(TextUtils.TruncateAt.MARQUEE); 1845 break; 1846 } 1847 1848 final boolean isPassword = password || passwordInputType || webPasswordInputType 1849 || numberPasswordInputType; 1850 final boolean isMonospaceEnforced = isPassword || (mEditor != null 1851 && (mEditor.mInputType 1852 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) 1853 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)); 1854 if (isMonospaceEnforced) { 1855 attributes.mTypefaceIndex = MONOSPACE; 1856 } 1857 1858 mFontWeightAdjustment = getContext().getResources().getConfiguration().fontWeightAdjustment; 1859 applyTextAppearance(attributes); 1860 1861 if (isPassword) { 1862 setTransformationMethod(PasswordTransformationMethod.getInstance()); 1863 } 1864 1865 // For addressing b/145128646 1866 // For the performance reason, we limit characters for single line text field. 1867 if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) { 1868 mSingleLineLengthFilter = new InputFilter.LengthFilter( 1869 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT); 1870 } 1871 1872 if (mSingleLineLengthFilter != null) { 1873 setFilters(new InputFilter[] { mSingleLineLengthFilter }); 1874 } else if (maxlength >= 0) { 1875 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 1876 } else { 1877 setFilters(NO_FILTERS); 1878 } 1879 1880 setText(text, bufferType); 1881 if (mText == null) { 1882 mText = ""; 1883 } 1884 if (mTransformed == null) { 1885 mTransformed = ""; 1886 } 1887 1888 if (textIsSetFromXml) { 1889 mTextSetFromXmlOrResourceId = true; 1890 } 1891 1892 if (hint != null) setHint(hint); 1893 1894 /* 1895 * Views are not normally clickable unless specified to be. 1896 * However, TextViews that have input or movement methods *are* 1897 * clickable by default. By setting clickable here, we implicitly set focusable as well 1898 * if not overridden by the developer. 1899 */ 1900 a = context.obtainStyledAttributes( 1901 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); 1902 boolean canInputOrMove = (mMovement != null || getKeyListener() != null); 1903 boolean clickable = canInputOrMove || isClickable(); 1904 boolean longClickable = canInputOrMove || isLongClickable(); 1905 int focusable = getFocusable(); 1906 boolean isAutoHandwritingEnabled = true; 1907 1908 n = a.getIndexCount(); 1909 for (int i = 0; i < n; i++) { 1910 int attr = a.getIndex(i); 1911 1912 switch (attr) { 1913 case com.android.internal.R.styleable.View_focusable: 1914 TypedValue val = new TypedValue(); 1915 if (a.getValue(attr, val)) { 1916 focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN) 1917 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE) 1918 : val.data; 1919 } 1920 break; 1921 1922 case com.android.internal.R.styleable.View_clickable: 1923 clickable = a.getBoolean(attr, clickable); 1924 break; 1925 1926 case com.android.internal.R.styleable.View_longClickable: 1927 longClickable = a.getBoolean(attr, longClickable); 1928 break; 1929 1930 case com.android.internal.R.styleable.View_autoHandwritingEnabled: 1931 isAutoHandwritingEnabled = a.getBoolean(attr, true); 1932 break; 1933 } 1934 } 1935 a.recycle(); 1936 1937 // Some apps were relying on the undefined behavior of focusable winning over 1938 // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually 1939 // when starting with EditText and setting only focusable=false). To keep those apps from 1940 // breaking, re-apply the focusable attribute here. 1941 if (focusable != getFocusable()) { 1942 setFocusable(focusable); 1943 } 1944 setClickable(clickable); 1945 setLongClickable(longClickable); 1946 setAutoHandwritingEnabled(isAutoHandwritingEnabled); 1947 1948 if (mEditor != null) mEditor.prepareCursorControllers(); 1949 1950 // If not explicitly specified this view is important for accessibility. 1951 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 1952 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 1953 } 1954 1955 if (supportsAutoSizeText()) { 1956 if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 1957 // If uniform auto-size has been specified but preset values have not been set then 1958 // replace the auto-size configuration values that have not been specified with the 1959 // defaults. 1960 if (!mHasPresetAutoSizeValues) { 1961 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1962 1963 if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1964 autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1965 TypedValue.COMPLEX_UNIT_SP, 1966 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1967 displayMetrics); 1968 } 1969 1970 if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1971 autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1972 TypedValue.COMPLEX_UNIT_SP, 1973 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1974 displayMetrics); 1975 } 1976 1977 if (autoSizeStepGranularityInPx 1978 == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1979 autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX; 1980 } 1981 1982 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1983 autoSizeMaxTextSizeInPx, 1984 autoSizeStepGranularityInPx); 1985 } 1986 1987 setupAutoSizeText(); 1988 } 1989 } else { 1990 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 1991 } 1992 1993 if (firstBaselineToTopHeight >= 0) { 1994 setFirstBaselineToTopHeight(firstBaselineToTopHeight); 1995 } 1996 if (lastBaselineToBottomHeight >= 0) { 1997 setLastBaselineToBottomHeight(lastBaselineToBottomHeight); 1998 } 1999 if (lineHeight >= 0) { 2000 if (lineHeightUnit == -1) { 2001 setLineHeightPx(lineHeight); 2002 } else { 2003 setLineHeight(lineHeightUnit, lineHeight); 2004 } 2005 } 2006 } 2007 2008 // Update mText and mPrecomputed 2009 private void setTextInternal(@Nullable CharSequence text) { 2010 mText = text; 2011 mSpannable = (text instanceof Spannable) ? (Spannable) text : null; 2012 mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 2013 } 2014 2015 /** 2016 * Specify whether this widget should automatically scale the text to try to perfectly fit 2017 * within the layout bounds by using the default auto-size configuration. 2018 * 2019 * @param autoSizeTextType the type of auto-size. Must be one of 2020 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 2021 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 2022 * 2023 * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above. 2024 * 2025 * @attr ref android.R.styleable#TextView_autoSizeTextType 2026 * 2027 * @see #getAutoSizeTextType() 2028 */ 2029 public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) { 2030 if (supportsAutoSizeText()) { 2031 switch (autoSizeTextType) { 2032 case AUTO_SIZE_TEXT_TYPE_NONE: 2033 clearAutoSizeConfiguration(); 2034 break; 2035 case AUTO_SIZE_TEXT_TYPE_UNIFORM: 2036 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 2037 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 2038 TypedValue.COMPLEX_UNIT_SP, 2039 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 2040 displayMetrics); 2041 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 2042 TypedValue.COMPLEX_UNIT_SP, 2043 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 2044 displayMetrics); 2045 2046 validateAndSetAutoSizeTextTypeUniformConfiguration( 2047 autoSizeMinTextSizeInPx, 2048 autoSizeMaxTextSizeInPx, 2049 DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX); 2050 if (setupAutoSizeText()) { 2051 autoSizeText(); 2052 invalidate(); 2053 } 2054 break; 2055 default: 2056 throw new IllegalArgumentException( 2057 "Unknown auto-size text type: " + autoSizeTextType); 2058 } 2059 } 2060 } 2061 2062 /** 2063 * Specify whether this widget should automatically scale the text to try to perfectly fit 2064 * within the layout bounds. If all the configuration params are valid the type of auto-size is 2065 * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 2066 * 2067 * @param autoSizeMinTextSize the minimum text size available for auto-size 2068 * @param autoSizeMaxTextSize the maximum text size available for auto-size 2069 * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with 2070 * the minimum and maximum text size in order to build the set of 2071 * text sizes the system uses to choose from when auto-sizing 2072 * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the 2073 * possible dimension units 2074 * 2075 * @throws IllegalArgumentException if any of the configuration params are invalid. 2076 * 2077 * @attr ref android.R.styleable#TextView_autoSizeTextType 2078 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 2079 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 2080 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 2081 * 2082 * @see #setAutoSizeTextTypeWithDefaults(int) 2083 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2084 * @see #getAutoSizeMinTextSize() 2085 * @see #getAutoSizeMaxTextSize() 2086 * @see #getAutoSizeStepGranularity() 2087 * @see #getAutoSizeTextAvailableSizes() 2088 */ 2089 public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, 2090 int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) { 2091 if (supportsAutoSizeText()) { 2092 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 2093 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 2094 unit, autoSizeMinTextSize, displayMetrics); 2095 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 2096 unit, autoSizeMaxTextSize, displayMetrics); 2097 final float autoSizeStepGranularityInPx = TypedValue.applyDimension( 2098 unit, autoSizeStepGranularity, displayMetrics); 2099 2100 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 2101 autoSizeMaxTextSizeInPx, 2102 autoSizeStepGranularityInPx); 2103 2104 if (setupAutoSizeText()) { 2105 autoSizeText(); 2106 invalidate(); 2107 } 2108 } 2109 } 2110 2111 /** 2112 * Specify whether this widget should automatically scale the text to try to perfectly fit 2113 * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid 2114 * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 2115 * 2116 * @param presetSizes an {@code int} array of sizes in pixels 2117 * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for 2118 * the possible dimension units 2119 * 2120 * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid. 2121 * 2122 * @attr ref android.R.styleable#TextView_autoSizeTextType 2123 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 2124 * 2125 * @see #setAutoSizeTextTypeWithDefaults(int) 2126 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2127 * @see #getAutoSizeMinTextSize() 2128 * @see #getAutoSizeMaxTextSize() 2129 * @see #getAutoSizeTextAvailableSizes() 2130 */ 2131 public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) { 2132 if (supportsAutoSizeText()) { 2133 final int presetSizesLength = presetSizes.length; 2134 if (presetSizesLength > 0) { 2135 int[] presetSizesInPx = new int[presetSizesLength]; 2136 2137 if (unit == TypedValue.COMPLEX_UNIT_PX) { 2138 presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength); 2139 } else { 2140 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 2141 // Convert all to sizes to pixels. 2142 for (int i = 0; i < presetSizesLength; i++) { 2143 presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit, 2144 presetSizes[i], displayMetrics)); 2145 } 2146 } 2147 2148 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx); 2149 if (!setupAutoSizeUniformPresetSizesConfiguration()) { 2150 throw new IllegalArgumentException("None of the preset sizes is valid: " 2151 + Arrays.toString(presetSizes)); 2152 } 2153 } else { 2154 mHasPresetAutoSizeValues = false; 2155 } 2156 2157 if (setupAutoSizeText()) { 2158 autoSizeText(); 2159 invalidate(); 2160 } 2161 } 2162 } 2163 2164 /** 2165 * Returns the type of auto-size set for this widget. 2166 * 2167 * @return an {@code int} corresponding to one of the auto-size types: 2168 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 2169 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 2170 * 2171 * @attr ref android.R.styleable#TextView_autoSizeTextType 2172 * 2173 * @see #setAutoSizeTextTypeWithDefaults(int) 2174 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2175 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2176 */ 2177 @InspectableProperty(enumMapping = { 2178 @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE), 2179 @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM) 2180 }) 2181 @AutoSizeTextType 2182 public int getAutoSizeTextType() { 2183 return mAutoSizeTextType; 2184 } 2185 2186 /** 2187 * @return the current auto-size step granularity in pixels. 2188 * 2189 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 2190 * 2191 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2192 */ 2193 @InspectableProperty 2194 public int getAutoSizeStepGranularity() { 2195 return Math.round(mAutoSizeStepGranularityInPx); 2196 } 2197 2198 /** 2199 * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that 2200 * if auto-size has not been configured this function returns {@code -1}. 2201 * 2202 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 2203 * 2204 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2205 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2206 */ 2207 @InspectableProperty 2208 public int getAutoSizeMinTextSize() { 2209 return Math.round(mAutoSizeMinTextSizeInPx); 2210 } 2211 2212 /** 2213 * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that 2214 * if auto-size has not been configured this function returns {@code -1}. 2215 * 2216 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 2217 * 2218 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2219 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2220 */ 2221 @InspectableProperty 2222 public int getAutoSizeMaxTextSize() { 2223 return Math.round(mAutoSizeMaxTextSizeInPx); 2224 } 2225 2226 /** 2227 * @return the current auto-size {@code int} sizes array (in pixels). 2228 * 2229 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2230 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2231 */ 2232 public int[] getAutoSizeTextAvailableSizes() { 2233 return mAutoSizeTextSizesInPx; 2234 } 2235 2236 private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) { 2237 final int textSizesLength = textSizes.length(); 2238 final int[] parsedSizes = new int[textSizesLength]; 2239 2240 if (textSizesLength > 0) { 2241 for (int i = 0; i < textSizesLength; i++) { 2242 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1); 2243 } 2244 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes); 2245 setupAutoSizeUniformPresetSizesConfiguration(); 2246 } 2247 } 2248 2249 private boolean setupAutoSizeUniformPresetSizesConfiguration() { 2250 final int sizesLength = mAutoSizeTextSizesInPx.length; 2251 mHasPresetAutoSizeValues = sizesLength > 0; 2252 if (mHasPresetAutoSizeValues) { 2253 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2254 mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0]; 2255 mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1]; 2256 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2257 } 2258 return mHasPresetAutoSizeValues; 2259 } 2260 2261 /** 2262 * If all params are valid then save the auto-size configuration. 2263 * 2264 * @throws IllegalArgumentException if any of the params are invalid 2265 */ 2266 private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, 2267 float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) { 2268 // First validate. 2269 if (autoSizeMinTextSizeInPx <= 0) { 2270 throw new IllegalArgumentException("Minimum auto-size text size (" 2271 + autoSizeMinTextSizeInPx + "px) is less or equal to (0px)"); 2272 } 2273 2274 if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) { 2275 throw new IllegalArgumentException("Maximum auto-size text size (" 2276 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size " 2277 + "text size (" + autoSizeMinTextSizeInPx + "px)"); 2278 } 2279 2280 if (autoSizeStepGranularityInPx <= 0) { 2281 throw new IllegalArgumentException("The auto-size step granularity (" 2282 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)"); 2283 } 2284 2285 // All good, persist the configuration. 2286 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2287 mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx; 2288 mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx; 2289 mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx; 2290 mHasPresetAutoSizeValues = false; 2291 } 2292 2293 private void clearAutoSizeConfiguration() { 2294 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 2295 mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2296 mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2297 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2298 mAutoSizeTextSizesInPx = EmptyArray.INT; 2299 mNeedsAutoSizeText = false; 2300 } 2301 2302 // Returns distinct sorted positive values. 2303 private int[] cleanupAutoSizePresetSizes(int[] presetValues) { 2304 final int presetValuesLength = presetValues.length; 2305 if (presetValuesLength == 0) { 2306 return presetValues; 2307 } 2308 Arrays.sort(presetValues); 2309 2310 final IntArray uniqueValidSizes = new IntArray(); 2311 for (int i = 0; i < presetValuesLength; i++) { 2312 final int currentPresetValue = presetValues[i]; 2313 2314 if (currentPresetValue > 0 2315 && uniqueValidSizes.binarySearch(currentPresetValue) < 0) { 2316 uniqueValidSizes.add(currentPresetValue); 2317 } 2318 } 2319 2320 return presetValuesLength == uniqueValidSizes.size() 2321 ? presetValues 2322 : uniqueValidSizes.toArray(); 2323 } 2324 2325 private boolean setupAutoSizeText() { 2326 if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 2327 // Calculate the sizes set based on minimum size, maximum size and step size if we do 2328 // not have a predefined set of sizes or if the current sizes array is empty. 2329 if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) { 2330 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx 2331 - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1; 2332 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength]; 2333 for (int i = 0; i < autoSizeValuesLength; i++) { 2334 autoSizeTextSizesInPx[i] = Math.round( 2335 mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx)); 2336 } 2337 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx); 2338 } 2339 2340 mNeedsAutoSizeText = true; 2341 } else { 2342 mNeedsAutoSizeText = false; 2343 } 2344 2345 return mNeedsAutoSizeText; 2346 } 2347 2348 private int[] parseDimensionArray(TypedArray dimens) { 2349 if (dimens == null) { 2350 return null; 2351 } 2352 int[] result = new int[dimens.length()]; 2353 for (int i = 0; i < result.length; i++) { 2354 result[i] = dimens.getDimensionPixelSize(i, 0); 2355 } 2356 return result; 2357 } 2358 2359 /** 2360 * @hide 2361 */ 2362 @TestApi 2363 @Override 2364 public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 2365 if (requestCode == PROCESS_TEXT_REQUEST_CODE) { 2366 if (resultCode == Activity.RESULT_OK && data != null) { 2367 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT); 2368 if (result != null) { 2369 if (isTextEditable()) { 2370 ClipData clip = ClipData.newPlainText("", result); 2371 ContentInfo payload = 2372 new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build(); 2373 performReceiveContent(payload); 2374 if (mEditor != null) { 2375 mEditor.refreshTextActionMode(); 2376 } 2377 } else { 2378 if (result.length() > 0) { 2379 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG) 2380 .show(); 2381 } 2382 } 2383 } 2384 } else if (mSpannable != null) { 2385 // Reset the selection. 2386 Selection.setSelection(mSpannable, getSelectionEnd()); 2387 } 2388 } 2389 } 2390 2391 /** 2392 * Sets the Typeface taking into account the given attributes. 2393 * 2394 * @param typeface a typeface 2395 * @param familyName family name string, e.g. "serif" 2396 * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF. 2397 * @param style a typeface style 2398 * @param weight a weight value for the Typeface or {@code FontStyle.FONT_WEIGHT_UNSPECIFIED} 2399 * if not specified. 2400 */ 2401 private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName, 2402 @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, 2403 @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) 2404 int weight) { 2405 if (typeface == null && familyName != null) { 2406 // Lookup normal Typeface from system font map. 2407 final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL); 2408 resolveStyleAndSetTypeface(normalTypeface, style, weight); 2409 } else if (typeface != null) { 2410 resolveStyleAndSetTypeface(typeface, style, weight); 2411 } else { // both typeface and familyName is null. 2412 switch (typefaceIndex) { 2413 case SANS: 2414 resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight); 2415 break; 2416 case SERIF: 2417 resolveStyleAndSetTypeface(Typeface.SERIF, style, weight); 2418 break; 2419 case MONOSPACE: 2420 resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight); 2421 break; 2422 case DEFAULT_TYPEFACE: 2423 default: 2424 resolveStyleAndSetTypeface(null, style, weight); 2425 break; 2426 } 2427 } 2428 } 2429 2430 private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style, 2431 @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) 2432 int weight) { 2433 if (weight >= 0) { 2434 weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight); 2435 final boolean italic = (style & Typeface.ITALIC) != 0; 2436 setTypeface(Typeface.create(typeface, weight, italic)); 2437 } else { 2438 setTypeface(typeface, style); 2439 } 2440 } 2441 2442 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) { 2443 boolean hasRelativeDrawables = (start != null) || (end != null); 2444 if (hasRelativeDrawables) { 2445 Drawables dr = mDrawables; 2446 if (dr == null) { 2447 mDrawables = dr = new Drawables(getContext()); 2448 } 2449 mDrawables.mOverride = true; 2450 final Rect compoundRect = dr.mCompoundRect; 2451 int[] state = getDrawableState(); 2452 if (start != null) { 2453 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 2454 start.setState(state); 2455 start.copyBounds(compoundRect); 2456 start.setCallback(this); 2457 2458 dr.mDrawableStart = start; 2459 dr.mDrawableSizeStart = compoundRect.width(); 2460 dr.mDrawableHeightStart = compoundRect.height(); 2461 } else { 2462 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2463 } 2464 if (end != null) { 2465 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 2466 end.setState(state); 2467 end.copyBounds(compoundRect); 2468 end.setCallback(this); 2469 2470 dr.mDrawableEnd = end; 2471 dr.mDrawableSizeEnd = compoundRect.width(); 2472 dr.mDrawableHeightEnd = compoundRect.height(); 2473 } else { 2474 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2475 } 2476 resetResolvedDrawables(); 2477 resolveDrawables(); 2478 applyCompoundDrawableTint(); 2479 } 2480 } 2481 2482 @android.view.RemotableViewMethod 2483 @Override 2484 public void setEnabled(boolean enabled) { 2485 if (enabled == isEnabled()) { 2486 return; 2487 } 2488 2489 if (!enabled) { 2490 // Hide the soft input if the currently active TextView is disabled 2491 InputMethodManager imm = getInputMethodManager(); 2492 if (imm != null) { 2493 imm.hideSoftInputFromView(this, 0); 2494 } 2495 } 2496 2497 super.setEnabled(enabled); 2498 2499 if (enabled) { 2500 // Make sure IME is updated with current editor info. 2501 InputMethodManager imm = getInputMethodManager(); 2502 if (imm != null) imm.restartInput(this); 2503 } 2504 2505 // Will change text color 2506 if (mEditor != null) { 2507 mEditor.invalidateTextDisplayList(); 2508 mEditor.prepareCursorControllers(); 2509 2510 // start or stop the cursor blinking as appropriate 2511 mEditor.makeBlink(); 2512 } 2513 } 2514 2515 /** 2516 * Sets the typeface and style in which the text should be displayed, 2517 * and turns on the fake bold and italic bits in the Paint if the 2518 * Typeface that you provided does not have all the bits in the 2519 * style that you specified. 2520 * 2521 * @attr ref android.R.styleable#TextView_typeface 2522 * @attr ref android.R.styleable#TextView_textStyle 2523 */ 2524 public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) { 2525 if (style > 0) { 2526 if (tf == null) { 2527 tf = Typeface.defaultFromStyle(style); 2528 } else { 2529 tf = Typeface.create(tf, style); 2530 } 2531 2532 setTypeface(tf); 2533 // now compute what (if any) algorithmic styling is needed 2534 int typefaceStyle = tf != null ? tf.getStyle() : 0; 2535 int need = style & ~typefaceStyle; 2536 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 2537 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 2538 } else { 2539 mTextPaint.setFakeBoldText(false); 2540 mTextPaint.setTextSkewX(0); 2541 setTypeface(tf); 2542 } 2543 } 2544 2545 /** 2546 * Subclasses override this to specify that they have a KeyListener 2547 * by default even if not specifically called for in the XML options. 2548 */ 2549 protected boolean getDefaultEditable() { 2550 return false; 2551 } 2552 2553 /** 2554 * Subclasses override this to specify a default movement method. 2555 */ 2556 protected MovementMethod getDefaultMovementMethod() { 2557 return null; 2558 } 2559 2560 /** 2561 * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called 2562 * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE} 2563 * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast 2564 * the return value from this method to Spannable or Editable, respectively. 2565 * 2566 * <p>The content of the return value should not be modified. If you want a modifiable one, you 2567 * should make your own copy first.</p> 2568 * 2569 * @return The text displayed by the text view. 2570 * @attr ref android.R.styleable#TextView_text 2571 */ 2572 @ViewDebug.CapturedViewProperty 2573 @InspectableProperty 2574 public CharSequence getText() { 2575 if (mUseTextPaddingForUiTranslation) { 2576 ViewTranslationCallback callback = getViewTranslationCallback(); 2577 if (callback != null && callback instanceof TextViewTranslationCallback) { 2578 TextViewTranslationCallback defaultCallback = 2579 (TextViewTranslationCallback) callback; 2580 if (defaultCallback.isTextPaddingEnabled() 2581 && defaultCallback.isShowingTranslation()) { 2582 return defaultCallback.getPaddedText(mText, mTransformed); 2583 } 2584 } 2585 } 2586 return mText; 2587 } 2588 2589 /** 2590 * Returns the length, in characters, of the text managed by this TextView 2591 * @return The length of the text managed by the TextView in characters. 2592 */ 2593 public int length() { 2594 return mText.length(); 2595 } 2596 2597 /** 2598 * Return the text that TextView is displaying as an Editable object. If the text is not 2599 * editable, null is returned. 2600 * 2601 * @see #getText 2602 */ 2603 public Editable getEditableText() { 2604 return (mText instanceof Editable) ? (Editable) mText : null; 2605 } 2606 2607 /** 2608 * @hide 2609 */ 2610 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 2611 public CharSequence getTransformed() { 2612 return mTransformed; 2613 } 2614 2615 /** 2616 * Gets the vertical distance between lines of text, in pixels. 2617 * Note that markup within the text can cause individual lines 2618 * to be taller or shorter than this height, and the layout may 2619 * contain additional first-or last-line padding. 2620 * @return The height of one standard line in pixels. 2621 */ 2622 @InspectableProperty 2623 public int getLineHeight() { 2624 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd); 2625 } 2626 2627 /** 2628 * Gets the {@link android.text.Layout} that is currently being used to display the text. 2629 * This value can be null if the text or width has recently changed. 2630 * @return The Layout that is currently being used to display the text. 2631 */ 2632 public final Layout getLayout() { 2633 return mLayout; 2634 } 2635 2636 /** 2637 * @return the {@link android.text.Layout} that is currently being used to 2638 * display the hint text. This can be null. 2639 */ 2640 @UnsupportedAppUsage 2641 final Layout getHintLayout() { 2642 return mHintLayout; 2643 } 2644 2645 /** 2646 * Retrieve the {@link android.content.UndoManager} that is currently associated 2647 * with this TextView. By default there is no associated UndoManager, so null 2648 * is returned. One can be associated with the TextView through 2649 * {@link #setUndoManager(android.content.UndoManager, String)} 2650 * 2651 * @hide 2652 */ 2653 public final UndoManager getUndoManager() { 2654 // TODO: Consider supporting a global undo manager. 2655 throw new UnsupportedOperationException("not implemented"); 2656 } 2657 2658 2659 /** 2660 * @hide 2661 */ 2662 @VisibleForTesting 2663 public final Editor getEditorForTesting() { 2664 return mEditor; 2665 } 2666 2667 /** 2668 * Associate an {@link android.content.UndoManager} with this TextView. Once 2669 * done, all edit operations on the TextView will result in appropriate 2670 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's 2671 * stack. 2672 * 2673 * @param undoManager The {@link android.content.UndoManager} to associate with 2674 * this TextView, or null to clear any existing association. 2675 * @param tag String tag identifying this particular TextView owner in the 2676 * UndoManager. This is used to keep the correct association with the 2677 * {@link android.content.UndoOwner} of any operations inside of the UndoManager. 2678 * 2679 * @hide 2680 */ 2681 public final void setUndoManager(UndoManager undoManager, String tag) { 2682 // TODO: Consider supporting a global undo manager. An implementation will need to: 2683 // * createEditorIfNeeded() 2684 // * Promote to BufferType.EDITABLE if needed. 2685 // * Update the UndoManager and UndoOwner. 2686 // Likewise it will need to be able to restore the default UndoManager. 2687 throw new UnsupportedOperationException("not implemented"); 2688 } 2689 2690 /** 2691 * Gets the current {@link KeyListener} for the TextView. 2692 * This will frequently be null for non-EditText TextViews. 2693 * @return the current key listener for this TextView. 2694 * 2695 * @attr ref android.R.styleable#TextView_numeric 2696 * @attr ref android.R.styleable#TextView_digits 2697 * @attr ref android.R.styleable#TextView_phoneNumber 2698 * @attr ref android.R.styleable#TextView_inputMethod 2699 * @attr ref android.R.styleable#TextView_capitalize 2700 * @attr ref android.R.styleable#TextView_autoText 2701 */ 2702 public final KeyListener getKeyListener() { 2703 return mEditor == null ? null : mEditor.mKeyListener; 2704 } 2705 2706 /** 2707 * Sets the key listener to be used with this TextView. This can be null 2708 * to disallow user input. Note that this method has significant and 2709 * subtle interactions with soft keyboards and other input method: 2710 * see {@link KeyListener#getInputType() KeyListener.getInputType()} 2711 * for important details. Calling this method will replace the current 2712 * content type of the text view with the content type returned by the 2713 * key listener. 2714 * <p> 2715 * Be warned that if you want a TextView with a key listener or movement 2716 * method not to be focusable, or if you want a TextView without a 2717 * key listener or movement method to be focusable, you must call 2718 * {@link #setFocusable} again after calling this to get the focusability 2719 * back the way you want it. 2720 * 2721 * @attr ref android.R.styleable#TextView_numeric 2722 * @attr ref android.R.styleable#TextView_digits 2723 * @attr ref android.R.styleable#TextView_phoneNumber 2724 * @attr ref android.R.styleable#TextView_inputMethod 2725 * @attr ref android.R.styleable#TextView_capitalize 2726 * @attr ref android.R.styleable#TextView_autoText 2727 */ 2728 public void setKeyListener(KeyListener input) { 2729 mListenerChanged = true; 2730 setKeyListenerOnly(input); 2731 fixFocusableAndClickableSettings(); 2732 2733 if (input != null) { 2734 createEditorIfNeeded(); 2735 setInputTypeFromEditor(); 2736 } else { 2737 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL; 2738 } 2739 2740 InputMethodManager imm = getInputMethodManager(); 2741 if (imm != null) imm.restartInput(this); 2742 } 2743 2744 private void setInputTypeFromEditor() { 2745 try { 2746 mEditor.mInputType = mEditor.mKeyListener.getInputType(); 2747 } catch (IncompatibleClassChangeError e) { 2748 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 2749 } 2750 // Change inputType, without affecting transformation. 2751 // No need to applySingleLine since mSingleLine is unchanged. 2752 setInputTypeSingleLine(mSingleLine); 2753 } 2754 2755 private void setKeyListenerOnly(KeyListener input) { 2756 if (mEditor == null && input == null) return; // null is the default value 2757 2758 createEditorIfNeeded(); 2759 if (mEditor.mKeyListener != input) { 2760 mEditor.mKeyListener = input; 2761 if (input != null && !(mText instanceof Editable)) { 2762 setText(mText); 2763 } 2764 2765 setFilters((Editable) mText, mFilters); 2766 } 2767 } 2768 2769 /** 2770 * Gets the {@link android.text.method.MovementMethod} being used for this TextView, 2771 * which provides positioning, scrolling, and text selection functionality. 2772 * This will frequently be null for non-EditText TextViews. 2773 * @return the movement method being used for this TextView. 2774 * @see android.text.method.MovementMethod 2775 */ 2776 public final MovementMethod getMovementMethod() { 2777 return mMovement; 2778 } 2779 2780 /** 2781 * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement 2782 * for this TextView. This can be null to disallow using the arrow keys to move the 2783 * cursor or scroll the view. 2784 * <p> 2785 * Be warned that if you want a TextView with a key listener or movement 2786 * method not to be focusable, or if you want a TextView without a 2787 * key listener or movement method to be focusable, you must call 2788 * {@link #setFocusable} again after calling this to get the focusability 2789 * back the way you want it. 2790 */ 2791 public final void setMovementMethod(MovementMethod movement) { 2792 if (mMovement != movement) { 2793 mMovement = movement; 2794 2795 if (movement != null && mSpannable == null) { 2796 setText(mText); 2797 } 2798 2799 fixFocusableAndClickableSettings(); 2800 2801 // SelectionModifierCursorController depends on textCanBeSelected, which depends on 2802 // mMovement 2803 if (mEditor != null) mEditor.prepareCursorControllers(); 2804 } 2805 } 2806 2807 private void fixFocusableAndClickableSettings() { 2808 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) { 2809 setFocusable(FOCUSABLE); 2810 setClickable(true); 2811 setLongClickable(true); 2812 } else { 2813 setFocusable(FOCUSABLE_AUTO); 2814 setClickable(false); 2815 setLongClickable(false); 2816 } 2817 } 2818 2819 /** 2820 * Gets the current {@link android.text.method.TransformationMethod} for the TextView. 2821 * This is frequently null, except for single-line and password fields. 2822 * @return the current transformation method for this TextView. 2823 * 2824 * @attr ref android.R.styleable#TextView_password 2825 * @attr ref android.R.styleable#TextView_singleLine 2826 */ 2827 public final TransformationMethod getTransformationMethod() { 2828 return mTransformation; 2829 } 2830 2831 /** 2832 * Sets the transformation that is applied to the text that this 2833 * TextView is displaying. 2834 * 2835 * @attr ref android.R.styleable#TextView_password 2836 * @attr ref android.R.styleable#TextView_singleLine 2837 */ 2838 public final void setTransformationMethod(TransformationMethod method) { 2839 if (mEditor != null) { 2840 mEditor.setTransformationMethod(method); 2841 } else { 2842 setTransformationMethodInternal(method, /* updateText */ true); 2843 } 2844 } 2845 2846 /** 2847 * Set the transformation that is applied to the text that this TextView is displaying, 2848 * optionally call the setText. 2849 * @param method the new transformation method to be set. 2850 * @param updateText whether the call {@link #setText} which will update the TextView to display 2851 * the new content. This method is helpful when updating 2852 * {@link TransformationMethod} inside {@link #setText}. It should only be 2853 * false if text will be updated immediately after this call, otherwise the 2854 * TextView will enter an inconsistent state. 2855 */ 2856 void setTransformationMethodInternal(@Nullable TransformationMethod method, 2857 boolean updateText) { 2858 if (method == mTransformation) { 2859 // Avoid the setText() below if the transformation is 2860 // the same. 2861 return; 2862 } 2863 if (mTransformation != null) { 2864 if (mSpannable != null) { 2865 mSpannable.removeSpan(mTransformation); 2866 } 2867 } 2868 2869 mTransformation = method; 2870 2871 if (method instanceof TransformationMethod2) { 2872 TransformationMethod2 method2 = (TransformationMethod2) method; 2873 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable); 2874 method2.setLengthChangesAllowed(mAllowTransformationLengthChange); 2875 } else { 2876 mAllowTransformationLengthChange = false; 2877 } 2878 2879 if (updateText) { 2880 if (Flags.insertModeNotUpdateSelection()) { 2881 // Update the transformation text. 2882 if (mTransformation == null) { 2883 mTransformed = mText; 2884 } else { 2885 mTransformed = mTransformation.getTransformation(mText, this); 2886 } 2887 if (mTransformed == null) { 2888 // Should not happen if the transformation method follows the non-null 2889 // postcondition. 2890 mTransformed = ""; 2891 } 2892 final boolean isOffsetMapping = mTransformed instanceof OffsetMapping; 2893 2894 // If the mText is a Spannable and the new TransformationMethod needs to listen to 2895 // its updates, apply the watcher on it. 2896 if (mTransformation != null && mText instanceof Spannable 2897 && (!mAllowTransformationLengthChange || isOffsetMapping)) { 2898 Spannable sp = (Spannable) mText; 2899 final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0; 2900 sp.setSpan(mTransformation, 0, mText.length(), 2901 Spanned.SPAN_INCLUSIVE_INCLUSIVE 2902 | (priority << Spanned.SPAN_PRIORITY_SHIFT)); 2903 } 2904 if (mLayout != null) { 2905 nullLayouts(); 2906 requestLayout(); 2907 invalidate(); 2908 } 2909 } else { 2910 setText(mText); 2911 } 2912 } 2913 2914 if (hasPasswordTransformationMethod()) { 2915 notifyViewAccessibilityStateChangedIfNeeded( 2916 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 2917 } 2918 2919 // PasswordTransformationMethod always have LTR text direction heuristics returned by 2920 // getTextDirectionHeuristic, needs reset 2921 mTextDir = getTextDirectionHeuristic(); 2922 } 2923 2924 /** 2925 * Returns the top padding of the view, plus space for the top 2926 * Drawable if any. 2927 */ 2928 public int getCompoundPaddingTop() { 2929 final Drawables dr = mDrawables; 2930 if (dr == null || dr.mShowing[Drawables.TOP] == null) { 2931 return mPaddingTop; 2932 } else { 2933 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 2934 } 2935 } 2936 2937 /** 2938 * Returns the bottom padding of the view, plus space for the bottom 2939 * Drawable if any. 2940 */ 2941 public int getCompoundPaddingBottom() { 2942 final Drawables dr = mDrawables; 2943 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) { 2944 return mPaddingBottom; 2945 } else { 2946 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 2947 } 2948 } 2949 2950 /** 2951 * Returns the left padding of the view, plus space for the left 2952 * Drawable if any. 2953 */ 2954 public int getCompoundPaddingLeft() { 2955 final Drawables dr = mDrawables; 2956 if (dr == null || dr.mShowing[Drawables.LEFT] == null) { 2957 return mPaddingLeft; 2958 } else { 2959 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 2960 } 2961 } 2962 2963 /** 2964 * Returns the right padding of the view, plus space for the right 2965 * Drawable if any. 2966 */ 2967 public int getCompoundPaddingRight() { 2968 final Drawables dr = mDrawables; 2969 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) { 2970 return mPaddingRight; 2971 } else { 2972 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 2973 } 2974 } 2975 2976 /** 2977 * Returns the start padding of the view, plus space for the start 2978 * Drawable if any. 2979 */ 2980 public int getCompoundPaddingStart() { 2981 resolveDrawables(); 2982 switch(getLayoutDirection()) { 2983 default: 2984 case LAYOUT_DIRECTION_LTR: 2985 return getCompoundPaddingLeft(); 2986 case LAYOUT_DIRECTION_RTL: 2987 return getCompoundPaddingRight(); 2988 } 2989 } 2990 2991 /** 2992 * Returns the end padding of the view, plus space for the end 2993 * Drawable if any. 2994 */ 2995 public int getCompoundPaddingEnd() { 2996 resolveDrawables(); 2997 switch(getLayoutDirection()) { 2998 default: 2999 case LAYOUT_DIRECTION_LTR: 3000 return getCompoundPaddingRight(); 3001 case LAYOUT_DIRECTION_RTL: 3002 return getCompoundPaddingLeft(); 3003 } 3004 } 3005 3006 /** 3007 * Returns the extended top padding of the view, including both the 3008 * top Drawable if any and any extra space to keep more than maxLines 3009 * of text from showing. It is only valid to call this after measuring. 3010 */ 3011 public int getExtendedPaddingTop() { 3012 if (mMaxMode != LINES) { 3013 return getCompoundPaddingTop(); 3014 } 3015 3016 if (mLayout == null) { 3017 assumeLayout(); 3018 } 3019 3020 if (mLayout.getLineCount() <= mMaximum) { 3021 return getCompoundPaddingTop(); 3022 } 3023 3024 int top = getCompoundPaddingTop(); 3025 int bottom = getCompoundPaddingBottom(); 3026 int viewht = getHeight() - top - bottom; 3027 int layoutht = mLayout.getLineTop(mMaximum); 3028 3029 if (layoutht >= viewht) { 3030 return top; 3031 } 3032 3033 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 3034 if (gravity == Gravity.TOP) { 3035 return top; 3036 } else if (gravity == Gravity.BOTTOM) { 3037 return top + viewht - layoutht; 3038 } else { // (gravity == Gravity.CENTER_VERTICAL) 3039 return top + (viewht - layoutht) / 2; 3040 } 3041 } 3042 3043 /** 3044 * Returns the extended bottom padding of the view, including both the 3045 * bottom Drawable if any and any extra space to keep more than maxLines 3046 * of text from showing. It is only valid to call this after measuring. 3047 */ 3048 public int getExtendedPaddingBottom() { 3049 if (mMaxMode != LINES) { 3050 return getCompoundPaddingBottom(); 3051 } 3052 3053 if (mLayout == null) { 3054 assumeLayout(); 3055 } 3056 3057 if (mLayout.getLineCount() <= mMaximum) { 3058 return getCompoundPaddingBottom(); 3059 } 3060 3061 int top = getCompoundPaddingTop(); 3062 int bottom = getCompoundPaddingBottom(); 3063 int viewht = getHeight() - top - bottom; 3064 int layoutht = mLayout.getLineTop(mMaximum); 3065 3066 if (layoutht >= viewht) { 3067 return bottom; 3068 } 3069 3070 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 3071 if (gravity == Gravity.TOP) { 3072 return bottom + viewht - layoutht; 3073 } else if (gravity == Gravity.BOTTOM) { 3074 return bottom; 3075 } else { // (gravity == Gravity.CENTER_VERTICAL) 3076 return bottom + (viewht - layoutht) / 2; 3077 } 3078 } 3079 3080 /** 3081 * Returns the total left padding of the view, including the left 3082 * Drawable if any. 3083 */ 3084 public int getTotalPaddingLeft() { 3085 return getCompoundPaddingLeft(); 3086 } 3087 3088 /** 3089 * Returns the total right padding of the view, including the right 3090 * Drawable if any. 3091 */ 3092 public int getTotalPaddingRight() { 3093 return getCompoundPaddingRight(); 3094 } 3095 3096 /** 3097 * Returns the total start padding of the view, including the start 3098 * Drawable if any. 3099 */ 3100 public int getTotalPaddingStart() { 3101 return getCompoundPaddingStart(); 3102 } 3103 3104 /** 3105 * Returns the total end padding of the view, including the end 3106 * Drawable if any. 3107 */ 3108 public int getTotalPaddingEnd() { 3109 return getCompoundPaddingEnd(); 3110 } 3111 3112 /** 3113 * Returns the total top padding of the view, including the top 3114 * Drawable if any, the extra space to keep more than maxLines 3115 * from showing, and the vertical offset for gravity, if any. 3116 */ 3117 public int getTotalPaddingTop() { 3118 return getExtendedPaddingTop() + getVerticalOffset(true); 3119 } 3120 3121 /** 3122 * Returns the total bottom padding of the view, including the bottom 3123 * Drawable if any, the extra space to keep more than maxLines 3124 * from showing, and the vertical offset for gravity, if any. 3125 */ 3126 public int getTotalPaddingBottom() { 3127 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 3128 } 3129 3130 /** 3131 * Sets the Drawables (if any) to appear to the left of, above, to the 3132 * right of, and below the text. Use {@code null} if you do not want a 3133 * Drawable there. The Drawables must already have had 3134 * {@link Drawable#setBounds} called. 3135 * <p> 3136 * Calling this method will overwrite any Drawables previously set using 3137 * {@link #setCompoundDrawablesRelative} or related methods. 3138 * 3139 * @attr ref android.R.styleable#TextView_drawableLeft 3140 * @attr ref android.R.styleable#TextView_drawableTop 3141 * @attr ref android.R.styleable#TextView_drawableRight 3142 * @attr ref android.R.styleable#TextView_drawableBottom 3143 */ 3144 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top, 3145 @Nullable Drawable right, @Nullable Drawable bottom) { 3146 Drawables dr = mDrawables; 3147 3148 // We're switching to absolute, discard relative. 3149 if (dr != null) { 3150 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 3151 dr.mDrawableStart = null; 3152 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); 3153 dr.mDrawableEnd = null; 3154 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3155 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3156 } 3157 3158 final boolean drawables = left != null || top != null || right != null || bottom != null; 3159 if (!drawables) { 3160 // Clearing drawables... can we free the data structure? 3161 if (dr != null) { 3162 if (!dr.hasMetadata()) { 3163 mDrawables = null; 3164 } else { 3165 // We need to retain the last set padding, so just clear 3166 // out all of the fields in the existing structure. 3167 for (int i = dr.mShowing.length - 1; i >= 0; i--) { 3168 if (dr.mShowing[i] != null) { 3169 dr.mShowing[i].setCallback(null); 3170 } 3171 dr.mShowing[i] = null; 3172 } 3173 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3174 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3175 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3176 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3177 } 3178 } 3179 } else { 3180 if (dr == null) { 3181 mDrawables = dr = new Drawables(getContext()); 3182 } 3183 3184 mDrawables.mOverride = false; 3185 3186 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) { 3187 dr.mShowing[Drawables.LEFT].setCallback(null); 3188 } 3189 dr.mShowing[Drawables.LEFT] = left; 3190 3191 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 3192 dr.mShowing[Drawables.TOP].setCallback(null); 3193 } 3194 dr.mShowing[Drawables.TOP] = top; 3195 3196 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) { 3197 dr.mShowing[Drawables.RIGHT].setCallback(null); 3198 } 3199 dr.mShowing[Drawables.RIGHT] = right; 3200 3201 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 3202 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3203 } 3204 dr.mShowing[Drawables.BOTTOM] = bottom; 3205 3206 final Rect compoundRect = dr.mCompoundRect; 3207 int[] state; 3208 3209 state = getDrawableState(); 3210 3211 if (left != null) { 3212 left.setState(state); 3213 left.copyBounds(compoundRect); 3214 left.setCallback(this); 3215 dr.mDrawableSizeLeft = compoundRect.width(); 3216 dr.mDrawableHeightLeft = compoundRect.height(); 3217 } else { 3218 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3219 } 3220 3221 if (right != null) { 3222 right.setState(state); 3223 right.copyBounds(compoundRect); 3224 right.setCallback(this); 3225 dr.mDrawableSizeRight = compoundRect.width(); 3226 dr.mDrawableHeightRight = compoundRect.height(); 3227 } else { 3228 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3229 } 3230 3231 if (top != null) { 3232 top.setState(state); 3233 top.copyBounds(compoundRect); 3234 top.setCallback(this); 3235 dr.mDrawableSizeTop = compoundRect.height(); 3236 dr.mDrawableWidthTop = compoundRect.width(); 3237 } else { 3238 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3239 } 3240 3241 if (bottom != null) { 3242 bottom.setState(state); 3243 bottom.copyBounds(compoundRect); 3244 bottom.setCallback(this); 3245 dr.mDrawableSizeBottom = compoundRect.height(); 3246 dr.mDrawableWidthBottom = compoundRect.width(); 3247 } else { 3248 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3249 } 3250 } 3251 3252 // Save initial left/right drawables 3253 if (dr != null) { 3254 dr.mDrawableLeftInitial = left; 3255 dr.mDrawableRightInitial = right; 3256 } 3257 3258 resetResolvedDrawables(); 3259 resolveDrawables(); 3260 applyCompoundDrawableTint(); 3261 invalidate(); 3262 requestLayout(); 3263 } 3264 3265 /** 3266 * Sets the Drawables (if any) to appear to the left of, above, to the 3267 * right of, and below the text. Use 0 if you do not want a Drawable there. 3268 * The Drawables' bounds will be set to their intrinsic bounds. 3269 * <p> 3270 * Calling this method will overwrite any Drawables previously set using 3271 * {@link #setCompoundDrawablesRelative} or related methods. 3272 * 3273 * @param left Resource identifier of the left Drawable. 3274 * @param top Resource identifier of the top Drawable. 3275 * @param right Resource identifier of the right Drawable. 3276 * @param bottom Resource identifier of the bottom Drawable. 3277 * 3278 * @attr ref android.R.styleable#TextView_drawableLeft 3279 * @attr ref android.R.styleable#TextView_drawableTop 3280 * @attr ref android.R.styleable#TextView_drawableRight 3281 * @attr ref android.R.styleable#TextView_drawableBottom 3282 */ 3283 @android.view.RemotableViewMethod 3284 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left, 3285 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { 3286 final Context context = getContext(); 3287 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null, 3288 top != 0 ? context.getDrawable(top) : null, 3289 right != 0 ? context.getDrawable(right) : null, 3290 bottom != 0 ? context.getDrawable(bottom) : null); 3291 } 3292 3293 /** 3294 * Sets the Drawables (if any) to appear to the left of, above, to the 3295 * right of, and below the text. Use {@code null} if you do not want a 3296 * Drawable there. The Drawables' bounds will be set to their intrinsic 3297 * bounds. 3298 * <p> 3299 * Calling this method will overwrite any Drawables previously set using 3300 * {@link #setCompoundDrawablesRelative} or related methods. 3301 * 3302 * @attr ref android.R.styleable#TextView_drawableLeft 3303 * @attr ref android.R.styleable#TextView_drawableTop 3304 * @attr ref android.R.styleable#TextView_drawableRight 3305 * @attr ref android.R.styleable#TextView_drawableBottom 3306 */ 3307 @android.view.RemotableViewMethod 3308 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left, 3309 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { 3310 3311 if (left != null) { 3312 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 3313 } 3314 if (right != null) { 3315 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 3316 } 3317 if (top != null) { 3318 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3319 } 3320 if (bottom != null) { 3321 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3322 } 3323 setCompoundDrawables(left, top, right, bottom); 3324 } 3325 3326 /** 3327 * Sets the Drawables (if any) to appear to the start of, above, to the end 3328 * of, and below the text. Use {@code null} if you do not want a Drawable 3329 * there. The Drawables must already have had {@link Drawable#setBounds} 3330 * called. 3331 * <p> 3332 * Calling this method will overwrite any Drawables previously set using 3333 * {@link #setCompoundDrawables} or related methods. 3334 * 3335 * @attr ref android.R.styleable#TextView_drawableStart 3336 * @attr ref android.R.styleable#TextView_drawableTop 3337 * @attr ref android.R.styleable#TextView_drawableEnd 3338 * @attr ref android.R.styleable#TextView_drawableBottom 3339 */ 3340 @android.view.RemotableViewMethod 3341 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top, 3342 @Nullable Drawable end, @Nullable Drawable bottom) { 3343 Drawables dr = mDrawables; 3344 3345 // We're switching to relative, discard absolute. 3346 if (dr != null) { 3347 if (dr.mShowing[Drawables.LEFT] != null) { 3348 dr.mShowing[Drawables.LEFT].setCallback(null); 3349 } 3350 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null; 3351 if (dr.mShowing[Drawables.RIGHT] != null) { 3352 dr.mShowing[Drawables.RIGHT].setCallback(null); 3353 } 3354 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null; 3355 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3356 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3357 } 3358 3359 final boolean drawables = start != null || top != null 3360 || end != null || bottom != null; 3361 3362 if (!drawables) { 3363 // Clearing drawables... can we free the data structure? 3364 if (dr != null) { 3365 if (!dr.hasMetadata()) { 3366 mDrawables = null; 3367 } else { 3368 // We need to retain the last set padding, so just clear 3369 // out all of the fields in the existing structure. 3370 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 3371 dr.mDrawableStart = null; 3372 if (dr.mShowing[Drawables.TOP] != null) { 3373 dr.mShowing[Drawables.TOP].setCallback(null); 3374 } 3375 dr.mShowing[Drawables.TOP] = null; 3376 if (dr.mDrawableEnd != null) { 3377 dr.mDrawableEnd.setCallback(null); 3378 } 3379 dr.mDrawableEnd = null; 3380 if (dr.mShowing[Drawables.BOTTOM] != null) { 3381 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3382 } 3383 dr.mShowing[Drawables.BOTTOM] = null; 3384 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3385 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3386 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3387 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3388 } 3389 } 3390 } else { 3391 if (dr == null) { 3392 mDrawables = dr = new Drawables(getContext()); 3393 } 3394 3395 mDrawables.mOverride = true; 3396 3397 if (dr.mDrawableStart != start && dr.mDrawableStart != null) { 3398 dr.mDrawableStart.setCallback(null); 3399 } 3400 dr.mDrawableStart = start; 3401 3402 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 3403 dr.mShowing[Drawables.TOP].setCallback(null); 3404 } 3405 dr.mShowing[Drawables.TOP] = top; 3406 3407 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) { 3408 dr.mDrawableEnd.setCallback(null); 3409 } 3410 dr.mDrawableEnd = end; 3411 3412 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 3413 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3414 } 3415 dr.mShowing[Drawables.BOTTOM] = bottom; 3416 3417 final Rect compoundRect = dr.mCompoundRect; 3418 int[] state; 3419 3420 state = getDrawableState(); 3421 3422 if (start != null) { 3423 start.setState(state); 3424 start.copyBounds(compoundRect); 3425 start.setCallback(this); 3426 dr.mDrawableSizeStart = compoundRect.width(); 3427 dr.mDrawableHeightStart = compoundRect.height(); 3428 } else { 3429 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3430 } 3431 3432 if (end != null) { 3433 end.setState(state); 3434 end.copyBounds(compoundRect); 3435 end.setCallback(this); 3436 dr.mDrawableSizeEnd = compoundRect.width(); 3437 dr.mDrawableHeightEnd = compoundRect.height(); 3438 } else { 3439 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3440 } 3441 3442 if (top != null) { 3443 top.setState(state); 3444 top.copyBounds(compoundRect); 3445 top.setCallback(this); 3446 dr.mDrawableSizeTop = compoundRect.height(); 3447 dr.mDrawableWidthTop = compoundRect.width(); 3448 } else { 3449 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3450 } 3451 3452 if (bottom != null) { 3453 bottom.setState(state); 3454 bottom.copyBounds(compoundRect); 3455 bottom.setCallback(this); 3456 dr.mDrawableSizeBottom = compoundRect.height(); 3457 dr.mDrawableWidthBottom = compoundRect.width(); 3458 } else { 3459 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3460 } 3461 } 3462 3463 resetResolvedDrawables(); 3464 resolveDrawables(); 3465 invalidate(); 3466 requestLayout(); 3467 } 3468 3469 /** 3470 * Sets the Drawables (if any) to appear to the start of, above, to the end 3471 * of, and below the text. Use 0 if you do not want a Drawable there. The 3472 * Drawables' bounds will be set to their intrinsic bounds. 3473 * <p> 3474 * Calling this method will overwrite any Drawables previously set using 3475 * {@link #setCompoundDrawables} or related methods. 3476 * 3477 * @param start Resource identifier of the start Drawable. 3478 * @param top Resource identifier of the top Drawable. 3479 * @param end Resource identifier of the end Drawable. 3480 * @param bottom Resource identifier of the bottom Drawable. 3481 * 3482 * @attr ref android.R.styleable#TextView_drawableStart 3483 * @attr ref android.R.styleable#TextView_drawableTop 3484 * @attr ref android.R.styleable#TextView_drawableEnd 3485 * @attr ref android.R.styleable#TextView_drawableBottom 3486 */ 3487 @android.view.RemotableViewMethod 3488 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start, 3489 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) { 3490 final Context context = getContext(); 3491 setCompoundDrawablesRelativeWithIntrinsicBounds( 3492 start != 0 ? context.getDrawable(start) : null, 3493 top != 0 ? context.getDrawable(top) : null, 3494 end != 0 ? context.getDrawable(end) : null, 3495 bottom != 0 ? context.getDrawable(bottom) : null); 3496 } 3497 3498 /** 3499 * Sets the Drawables (if any) to appear to the start of, above, to the end 3500 * of, and below the text. Use {@code null} if you do not want a Drawable 3501 * there. The Drawables' bounds will be set to their intrinsic bounds. 3502 * <p> 3503 * Calling this method will overwrite any Drawables previously set using 3504 * {@link #setCompoundDrawables} or related methods. 3505 * 3506 * @attr ref android.R.styleable#TextView_drawableStart 3507 * @attr ref android.R.styleable#TextView_drawableTop 3508 * @attr ref android.R.styleable#TextView_drawableEnd 3509 * @attr ref android.R.styleable#TextView_drawableBottom 3510 */ 3511 @android.view.RemotableViewMethod 3512 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start, 3513 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { 3514 3515 if (start != null) { 3516 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 3517 } 3518 if (end != null) { 3519 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 3520 } 3521 if (top != null) { 3522 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3523 } 3524 if (bottom != null) { 3525 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3526 } 3527 setCompoundDrawablesRelative(start, top, end, bottom); 3528 } 3529 3530 /** 3531 * Returns drawables for the left, top, right, and bottom borders. 3532 * 3533 * @attr ref android.R.styleable#TextView_drawableLeft 3534 * @attr ref android.R.styleable#TextView_drawableTop 3535 * @attr ref android.R.styleable#TextView_drawableRight 3536 * @attr ref android.R.styleable#TextView_drawableBottom 3537 */ 3538 @NonNull 3539 public Drawable[] getCompoundDrawables() { 3540 final Drawables dr = mDrawables; 3541 if (dr != null) { 3542 return dr.mShowing.clone(); 3543 } else { 3544 return new Drawable[] { null, null, null, null }; 3545 } 3546 } 3547 3548 /** 3549 * Returns drawables for the start, top, end, and bottom borders. 3550 * 3551 * @attr ref android.R.styleable#TextView_drawableStart 3552 * @attr ref android.R.styleable#TextView_drawableTop 3553 * @attr ref android.R.styleable#TextView_drawableEnd 3554 * @attr ref android.R.styleable#TextView_drawableBottom 3555 */ 3556 @NonNull 3557 public Drawable[] getCompoundDrawablesRelative() { 3558 final Drawables dr = mDrawables; 3559 if (dr != null) { 3560 return new Drawable[] { 3561 dr.mDrawableStart, dr.mShowing[Drawables.TOP], 3562 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM] 3563 }; 3564 } else { 3565 return new Drawable[] { null, null, null, null }; 3566 } 3567 } 3568 3569 /** 3570 * Sets the size of the padding between the compound drawables and 3571 * the text. 3572 * 3573 * @attr ref android.R.styleable#TextView_drawablePadding 3574 */ 3575 @android.view.RemotableViewMethod 3576 public void setCompoundDrawablePadding(int pad) { 3577 Drawables dr = mDrawables; 3578 if (pad == 0) { 3579 if (dr != null) { 3580 dr.mDrawablePadding = pad; 3581 } 3582 } else { 3583 if (dr == null) { 3584 mDrawables = dr = new Drawables(getContext()); 3585 } 3586 dr.mDrawablePadding = pad; 3587 } 3588 3589 invalidate(); 3590 requestLayout(); 3591 } 3592 3593 /** 3594 * Returns the padding between the compound drawables and the text. 3595 * 3596 * @attr ref android.R.styleable#TextView_drawablePadding 3597 */ 3598 @InspectableProperty(name = "drawablePadding") 3599 public int getCompoundDrawablePadding() { 3600 final Drawables dr = mDrawables; 3601 return dr != null ? dr.mDrawablePadding : 0; 3602 } 3603 3604 /** 3605 * Applies a tint to the compound drawables. Does not modify the 3606 * current tint mode, which is {@link BlendMode#SRC_IN} by default. 3607 * <p> 3608 * Subsequent calls to 3609 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} 3610 * and related methods will automatically mutate the drawables and apply 3611 * the specified tint and tint mode using 3612 * {@link Drawable#setTintList(ColorStateList)}. 3613 * 3614 * @param tint the tint to apply, may be {@code null} to clear tint 3615 * 3616 * @attr ref android.R.styleable#TextView_drawableTint 3617 * @see #getCompoundDrawableTintList() 3618 * @see Drawable#setTintList(ColorStateList) 3619 */ 3620 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) { 3621 if (mDrawables == null) { 3622 mDrawables = new Drawables(getContext()); 3623 } 3624 mDrawables.mTintList = tint; 3625 mDrawables.mHasTint = true; 3626 3627 applyCompoundDrawableTint(); 3628 } 3629 3630 /** 3631 * @return the tint applied to the compound drawables 3632 * @attr ref android.R.styleable#TextView_drawableTint 3633 * @see #setCompoundDrawableTintList(ColorStateList) 3634 */ 3635 @InspectableProperty(name = "drawableTint") 3636 public ColorStateList getCompoundDrawableTintList() { 3637 return mDrawables != null ? mDrawables.mTintList : null; 3638 } 3639 3640 /** 3641 * Specifies the blending mode used to apply the tint specified by 3642 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3643 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3644 * 3645 * @param tintMode the blending mode used to apply the tint, may be 3646 * {@code null} to clear tint 3647 * @attr ref android.R.styleable#TextView_drawableTintMode 3648 * @see #setCompoundDrawableTintList(ColorStateList) 3649 * @see Drawable#setTintMode(PorterDuff.Mode) 3650 */ 3651 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) { 3652 setCompoundDrawableTintBlendMode(tintMode != null 3653 ? BlendMode.fromValue(tintMode.nativeInt) : null); 3654 } 3655 3656 /** 3657 * Specifies the blending mode used to apply the tint specified by 3658 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3659 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3660 * 3661 * @param blendMode the blending mode used to apply the tint, may be 3662 * {@code null} to clear tint 3663 * @attr ref android.R.styleable#TextView_drawableTintMode 3664 * @see #setCompoundDrawableTintList(ColorStateList) 3665 * @see Drawable#setTintBlendMode(BlendMode) 3666 */ 3667 public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) { 3668 if (mDrawables == null) { 3669 mDrawables = new Drawables(getContext()); 3670 } 3671 mDrawables.mBlendMode = blendMode; 3672 mDrawables.mHasTintMode = true; 3673 3674 applyCompoundDrawableTint(); 3675 } 3676 3677 /** 3678 * Returns the blending mode used to apply the tint to the compound 3679 * drawables, if specified. 3680 * 3681 * @return the blending mode used to apply the tint to the compound 3682 * drawables 3683 * @attr ref android.R.styleable#TextView_drawableTintMode 3684 * @see #setCompoundDrawableTintMode(PorterDuff.Mode) 3685 * 3686 */ 3687 @InspectableProperty(name = "drawableTintMode") 3688 public PorterDuff.Mode getCompoundDrawableTintMode() { 3689 BlendMode mode = getCompoundDrawableTintBlendMode(); 3690 return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; 3691 } 3692 3693 /** 3694 * Returns the blending mode used to apply the tint to the compound 3695 * drawables, if specified. 3696 * 3697 * @return the blending mode used to apply the tint to the compound 3698 * drawables 3699 * @attr ref android.R.styleable#TextView_drawableTintMode 3700 * @see #setCompoundDrawableTintBlendMode(BlendMode) 3701 */ 3702 @InspectableProperty(name = "drawableBlendMode", 3703 attributeId = com.android.internal.R.styleable.TextView_drawableTintMode) 3704 public @Nullable BlendMode getCompoundDrawableTintBlendMode() { 3705 return mDrawables != null ? mDrawables.mBlendMode : null; 3706 } 3707 3708 private void applyCompoundDrawableTint() { 3709 if (mDrawables == null) { 3710 return; 3711 } 3712 3713 if (mDrawables.mHasTint || mDrawables.mHasTintMode) { 3714 final ColorStateList tintList = mDrawables.mTintList; 3715 final BlendMode blendMode = mDrawables.mBlendMode; 3716 final boolean hasTint = mDrawables.mHasTint; 3717 final boolean hasTintMode = mDrawables.mHasTintMode; 3718 final int[] state = getDrawableState(); 3719 3720 for (Drawable dr : mDrawables.mShowing) { 3721 if (dr == null) { 3722 continue; 3723 } 3724 3725 if (dr == mDrawables.mDrawableError) { 3726 // From a developer's perspective, the error drawable isn't 3727 // a compound drawable. Don't apply the generic compound 3728 // drawable tint to it. 3729 continue; 3730 } 3731 3732 dr.mutate(); 3733 3734 if (hasTint) { 3735 dr.setTintList(tintList); 3736 } 3737 3738 if (hasTintMode) { 3739 dr.setTintBlendMode(blendMode); 3740 } 3741 3742 // The drawable (or one of its children) may not have been 3743 // stateful before applying the tint, so let's try again. 3744 if (dr.isStateful()) { 3745 dr.setState(state); 3746 } 3747 } 3748 } 3749 } 3750 3751 /** 3752 * @inheritDoc 3753 * 3754 * @see #setFirstBaselineToTopHeight(int) 3755 * @see #setLastBaselineToBottomHeight(int) 3756 */ 3757 @Override 3758 public void setPadding(int left, int top, int right, int bottom) { 3759 if (left != mPaddingLeft 3760 || right != mPaddingRight 3761 || top != mPaddingTop 3762 || bottom != mPaddingBottom) { 3763 nullLayouts(); 3764 } 3765 3766 // the super call will requestLayout() 3767 super.setPadding(left, top, right, bottom); 3768 invalidate(); 3769 } 3770 3771 /** 3772 * @inheritDoc 3773 * 3774 * @see #setFirstBaselineToTopHeight(int) 3775 * @see #setLastBaselineToBottomHeight(int) 3776 */ 3777 @Override 3778 public void setPaddingRelative(int start, int top, int end, int bottom) { 3779 if (start != getPaddingStart() 3780 || end != getPaddingEnd() 3781 || top != mPaddingTop 3782 || bottom != mPaddingBottom) { 3783 nullLayouts(); 3784 } 3785 3786 // the super call will requestLayout() 3787 super.setPaddingRelative(start, top, end, bottom); 3788 invalidate(); 3789 } 3790 3791 /** 3792 * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is 3793 * the distance between the top of the TextView and first line's baseline. 3794 * <p> 3795 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3796 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3797 * 3798 * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was 3799 * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated. 3800 * Moreover since this function sets the top padding, if the height of the TextView is less than 3801 * the sum of top padding, line height and bottom padding, top of the line will be pushed 3802 * down and bottom will be clipped. 3803 * 3804 * @param firstBaselineToTopHeight distance between first baseline to top of the container 3805 * in pixels 3806 * 3807 * @see #getFirstBaselineToTopHeight() 3808 * @see #setLastBaselineToBottomHeight(int) 3809 * @see #setPadding(int, int, int, int) 3810 * @see #setPaddingRelative(int, int, int, int) 3811 * 3812 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3813 */ 3814 public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) { 3815 Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight); 3816 3817 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3818 final int fontMetricsTop; 3819 if (getIncludeFontPadding()) { 3820 fontMetricsTop = fontMetrics.top; 3821 } else { 3822 fontMetricsTop = fontMetrics.ascent; 3823 } 3824 3825 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3826 // in settings). At the moment, we don't. 3827 3828 if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) { 3829 final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop); 3830 setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom()); 3831 } 3832 } 3833 3834 /** 3835 * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is 3836 * the distance between the bottom of the TextView and the last line's baseline. 3837 * <p> 3838 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3839 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3840 * 3841 * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was 3842 * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated. 3843 * Moreover since this function sets the bottom padding, if the height of the TextView is less 3844 * than the sum of top padding, line height and bottom padding, bottom of the text will be 3845 * clipped. 3846 * 3847 * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container 3848 * in pixels 3849 * 3850 * @see #getLastBaselineToBottomHeight() 3851 * @see #setFirstBaselineToTopHeight(int) 3852 * @see #setPadding(int, int, int, int) 3853 * @see #setPaddingRelative(int, int, int, int) 3854 * 3855 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3856 */ 3857 public void setLastBaselineToBottomHeight( 3858 @Px @IntRange(from = 0) int lastBaselineToBottomHeight) { 3859 Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight); 3860 3861 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3862 final int fontMetricsBottom; 3863 if (getIncludeFontPadding()) { 3864 fontMetricsBottom = fontMetrics.bottom; 3865 } else { 3866 fontMetricsBottom = fontMetrics.descent; 3867 } 3868 3869 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3870 // in settings). At the moment, we don't. 3871 3872 if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) { 3873 final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom; 3874 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom); 3875 } 3876 } 3877 3878 /** 3879 * Returns the distance between the first text baseline and the top of this TextView. 3880 * 3881 * @see #setFirstBaselineToTopHeight(int) 3882 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3883 */ 3884 @InspectableProperty 3885 public int getFirstBaselineToTopHeight() { 3886 return getPaddingTop() - getPaint().getFontMetricsInt().top; 3887 } 3888 3889 /** 3890 * Returns the distance between the last text baseline and the bottom of this TextView. 3891 * 3892 * @see #setLastBaselineToBottomHeight(int) 3893 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3894 */ 3895 @InspectableProperty 3896 public int getLastBaselineToBottomHeight() { 3897 return getPaddingBottom() + getPaint().getFontMetricsInt().bottom; 3898 } 3899 3900 /** 3901 * Gets the autolink mask of the text. 3902 * 3903 * See {@link Linkify#ALL} and peers for possible values. 3904 * 3905 * @attr ref android.R.styleable#TextView_autoLink 3906 */ 3907 @InspectableProperty(name = "autoLink", flagMapping = { 3908 @FlagEntry(name = "web", target = Linkify.WEB_URLS), 3909 @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES), 3910 @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS), 3911 @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES) 3912 }) 3913 public final int getAutoLinkMask() { 3914 return mAutoLinkMask; 3915 } 3916 3917 /** 3918 * Sets the Drawable corresponding to the selection handle used for 3919 * positioning the cursor within text. The Drawable defaults to the value 3920 * of the textSelectHandle 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 #setTextSelectHandle(int) 3925 * @attr ref android.R.styleable#TextView_textSelectHandle 3926 */ 3927 @android.view.RemotableViewMethod 3928 public void setTextSelectHandle(@NonNull Drawable textSelectHandle) { 3929 Preconditions.checkNotNull(textSelectHandle, 3930 "The text select handle should not be null."); 3931 mTextSelectHandle = textSelectHandle; 3932 mTextSelectHandleRes = 0; 3933 if (mEditor != null) { 3934 mEditor.loadHandleDrawables(true /* overwrite */); 3935 } 3936 } 3937 3938 /** 3939 * Sets the Drawable corresponding to the selection handle used for 3940 * positioning the cursor within text. The Drawable defaults to the value 3941 * of the textSelectHandle attribute. 3942 * Note that any change applied to the handle Drawable will not be visible 3943 * until the handle is hidden and then drawn again. 3944 * 3945 * @see #setTextSelectHandle(Drawable) 3946 * @attr ref android.R.styleable#TextView_textSelectHandle 3947 */ 3948 @android.view.RemotableViewMethod 3949 public void setTextSelectHandle(@DrawableRes int textSelectHandle) { 3950 Preconditions.checkArgument(textSelectHandle != 0, 3951 "The text select handle should be a valid drawable resource id."); 3952 setTextSelectHandle(mContext.getDrawable(textSelectHandle)); 3953 } 3954 3955 /** 3956 * Returns the Drawable corresponding to the selection handle used 3957 * for positioning the cursor within text. 3958 * Note that any change applied to the handle Drawable will not be visible 3959 * until the handle is hidden and then drawn again. 3960 * 3961 * @return the text select handle drawable 3962 * 3963 * @see #setTextSelectHandle(Drawable) 3964 * @see #setTextSelectHandle(int) 3965 * @attr ref android.R.styleable#TextView_textSelectHandle 3966 */ 3967 @Nullable public Drawable getTextSelectHandle() { 3968 if (mTextSelectHandle == null && mTextSelectHandleRes != 0) { 3969 mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes); 3970 } 3971 return mTextSelectHandle; 3972 } 3973 3974 /** 3975 * Sets the Drawable corresponding to the left handle used 3976 * for selecting text. The Drawable defaults to the value of the 3977 * textSelectHandleLeft 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 #setTextSelectHandleLeft(int) 3982 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3983 */ 3984 @android.view.RemotableViewMethod 3985 public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) { 3986 Preconditions.checkNotNull(textSelectHandleLeft, 3987 "The left text select handle should not be null."); 3988 mTextSelectHandleLeft = textSelectHandleLeft; 3989 mTextSelectHandleLeftRes = 0; 3990 if (mEditor != null) { 3991 mEditor.loadHandleDrawables(true /* overwrite */); 3992 } 3993 } 3994 3995 /** 3996 * Sets the Drawable corresponding to the left handle used 3997 * for selecting text. The Drawable defaults to the value of the 3998 * textSelectHandleLeft attribute. 3999 * Note that any change applied to the handle Drawable will not be visible 4000 * until the handle is hidden and then drawn again. 4001 * 4002 * @see #setTextSelectHandleLeft(Drawable) 4003 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 4004 */ 4005 @android.view.RemotableViewMethod 4006 public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) { 4007 Preconditions.checkArgument(textSelectHandleLeft != 0, 4008 "The text select left handle should be a valid drawable resource id."); 4009 setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft)); 4010 } 4011 4012 /** 4013 * Returns the Drawable corresponding to the left handle used 4014 * for selecting text. 4015 * Note that any change applied to the handle Drawable will not be visible 4016 * until the handle is hidden and then drawn again. 4017 * 4018 * @return the left text selection handle drawable 4019 * 4020 * @see #setTextSelectHandleLeft(Drawable) 4021 * @see #setTextSelectHandleLeft(int) 4022 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 4023 */ 4024 @Nullable public Drawable getTextSelectHandleLeft() { 4025 if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) { 4026 mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes); 4027 } 4028 return mTextSelectHandleLeft; 4029 } 4030 4031 /** 4032 * Sets the Drawable corresponding to the right handle used 4033 * for selecting text. The Drawable defaults to the value of the 4034 * textSelectHandleRight attribute. 4035 * Note that any change applied to the handle Drawable will not be visible 4036 * until the handle is hidden and then drawn again. 4037 * 4038 * @see #setTextSelectHandleRight(int) 4039 * @attr ref android.R.styleable#TextView_textSelectHandleRight 4040 */ 4041 @android.view.RemotableViewMethod 4042 public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) { 4043 Preconditions.checkNotNull(textSelectHandleRight, 4044 "The right text select handle should not be null."); 4045 mTextSelectHandleRight = textSelectHandleRight; 4046 mTextSelectHandleRightRes = 0; 4047 if (mEditor != null) { 4048 mEditor.loadHandleDrawables(true /* overwrite */); 4049 } 4050 } 4051 4052 /** 4053 * Sets the Drawable corresponding to the right handle used 4054 * for selecting text. The Drawable defaults to the value of the 4055 * textSelectHandleRight attribute. 4056 * Note that any change applied to the handle Drawable will not be visible 4057 * until the handle is hidden and then drawn again. 4058 * 4059 * @see #setTextSelectHandleRight(Drawable) 4060 * @attr ref android.R.styleable#TextView_textSelectHandleRight 4061 */ 4062 @android.view.RemotableViewMethod 4063 public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) { 4064 Preconditions.checkArgument(textSelectHandleRight != 0, 4065 "The text select right handle should be a valid drawable resource id."); 4066 setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight)); 4067 } 4068 4069 /** 4070 * Returns the Drawable corresponding to the right handle used 4071 * for selecting text. 4072 * Note that any change applied to the handle Drawable will not be visible 4073 * until the handle is hidden and then drawn again. 4074 * 4075 * @return the right text selection handle drawable 4076 * 4077 * @see #setTextSelectHandleRight(Drawable) 4078 * @see #setTextSelectHandleRight(int) 4079 * @attr ref android.R.styleable#TextView_textSelectHandleRight 4080 */ 4081 @Nullable public Drawable getTextSelectHandleRight() { 4082 if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) { 4083 mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes); 4084 } 4085 return mTextSelectHandleRight; 4086 } 4087 4088 /** 4089 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 4090 * value of the textCursorDrawable attribute. 4091 * Note that any change applied to the cursor Drawable will not be visible 4092 * until the cursor is hidden and then drawn again. 4093 * 4094 * @see #setTextCursorDrawable(int) 4095 * @attr ref android.R.styleable#TextView_textCursorDrawable 4096 */ 4097 public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) { 4098 mCursorDrawable = textCursorDrawable; 4099 mCursorDrawableRes = 0; 4100 if (mEditor != null) { 4101 mEditor.loadCursorDrawable(); 4102 } 4103 } 4104 4105 /** 4106 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 4107 * value of the textCursorDrawable attribute. 4108 * Note that any change applied to the cursor Drawable will not be visible 4109 * until the cursor is hidden and then drawn again. 4110 * 4111 * @see #setTextCursorDrawable(Drawable) 4112 * @attr ref android.R.styleable#TextView_textCursorDrawable 4113 */ 4114 public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) { 4115 setTextCursorDrawable( 4116 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null); 4117 } 4118 4119 /** 4120 * Returns the Drawable corresponding to the text cursor. 4121 * Note that any change applied to the cursor Drawable will not be visible 4122 * until the cursor is hidden and then drawn again. 4123 * 4124 * @return the text cursor drawable 4125 * 4126 * @see #setTextCursorDrawable(Drawable) 4127 * @see #setTextCursorDrawable(int) 4128 * @attr ref android.R.styleable#TextView_textCursorDrawable 4129 */ 4130 @Nullable public Drawable getTextCursorDrawable() { 4131 if (mCursorDrawable == null && mCursorDrawableRes != 0) { 4132 mCursorDrawable = mContext.getDrawable(mCursorDrawableRes); 4133 } 4134 return mCursorDrawable; 4135 } 4136 4137 /** 4138 * Sets the text appearance from the specified style resource. 4139 * <p> 4140 * Use a framework-defined {@code TextAppearance} style like 4141 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1} 4142 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the 4143 * set of attributes that can be used in a custom style. 4144 * 4145 * @param resId the resource identifier of the style to apply 4146 * @attr ref android.R.styleable#TextView_textAppearance 4147 */ 4148 @SuppressWarnings("deprecation") 4149 public void setTextAppearance(@StyleRes int resId) { 4150 setTextAppearance(mContext, resId); 4151 } 4152 4153 /** 4154 * Sets the text color, size, style, hint color, and highlight color 4155 * from the specified TextAppearance resource. 4156 * 4157 * @deprecated Use {@link #setTextAppearance(int)} instead. 4158 */ 4159 @Deprecated 4160 public void setTextAppearance(Context context, @StyleRes int resId) { 4161 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance); 4162 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 4163 readTextAppearance(context, ta, attributes, false /* styleArray */); 4164 ta.recycle(); 4165 applyTextAppearance(attributes); 4166 } 4167 4168 /** 4169 * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code 4170 * that reads these attributes in the constructor and in {@link #setTextAppearance}. 4171 */ 4172 private static class TextAppearanceAttributes { 4173 int mTextColorHighlight = 0; 4174 int mSearchResultHighlightColor = 0; 4175 int mFocusedSearchResultHighlightColor = 0; 4176 ColorStateList mTextColor = null; 4177 ColorStateList mTextColorHint = null; 4178 ColorStateList mTextColorLink = null; 4179 int mTextSize = -1; 4180 int mTextSizeUnit = -1; 4181 LocaleList mTextLocales = null; 4182 String mFontFamily = null; 4183 Typeface mFontTypeface = null; 4184 boolean mFontFamilyExplicit = false; 4185 int mTypefaceIndex = -1; 4186 int mTextStyle = 0; 4187 int mFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED; 4188 boolean mAllCaps = false; 4189 int mShadowColor = 0; 4190 float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0; 4191 boolean mHasElegant = false; 4192 boolean mElegant = false; 4193 boolean mHasFallbackLineSpacing = false; 4194 boolean mFallbackLineSpacing = false; 4195 boolean mHasLetterSpacing = false; 4196 float mLetterSpacing = 0; 4197 String mFontFeatureSettings = null; 4198 String mFontVariationSettings = null; 4199 boolean mHasLineBreakStyle = false; 4200 boolean mHasLineBreakWordStyle = false; 4201 int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; 4202 int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; 4203 4204 @Override 4205 public String toString() { 4206 return "TextAppearanceAttributes {\n" 4207 + " mTextColorHighlight:" + mTextColorHighlight + "\n" 4208 + " mSearchResultHighlightColor: " + mSearchResultHighlightColor + "\n" 4209 + " mFocusedSearchResultHighlightColor: " 4210 + mFocusedSearchResultHighlightColor + "\n" 4211 + " mTextColor:" + mTextColor + "\n" 4212 + " mTextColorHint:" + mTextColorHint + "\n" 4213 + " mTextColorLink:" + mTextColorLink + "\n" 4214 + " mTextSize:" + mTextSize + "\n" 4215 + " mTextSizeUnit:" + mTextSizeUnit + "\n" 4216 + " mTextLocales:" + mTextLocales + "\n" 4217 + " mFontFamily:" + mFontFamily + "\n" 4218 + " mFontTypeface:" + mFontTypeface + "\n" 4219 + " mFontFamilyExplicit:" + mFontFamilyExplicit + "\n" 4220 + " mTypefaceIndex:" + mTypefaceIndex + "\n" 4221 + " mTextStyle:" + mTextStyle + "\n" 4222 + " mFontWeight:" + mFontWeight + "\n" 4223 + " mAllCaps:" + mAllCaps + "\n" 4224 + " mShadowColor:" + mShadowColor + "\n" 4225 + " mShadowDx:" + mShadowDx + "\n" 4226 + " mShadowDy:" + mShadowDy + "\n" 4227 + " mShadowRadius:" + mShadowRadius + "\n" 4228 + " mHasElegant:" + mHasElegant + "\n" 4229 + " mElegant:" + mElegant + "\n" 4230 + " mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n" 4231 + " mFallbackLineSpacing:" + mFallbackLineSpacing + "\n" 4232 + " mHasLetterSpacing:" + mHasLetterSpacing + "\n" 4233 + " mLetterSpacing:" + mLetterSpacing + "\n" 4234 + " mFontFeatureSettings:" + mFontFeatureSettings + "\n" 4235 + " mFontVariationSettings:" + mFontVariationSettings + "\n" 4236 + " mHasLineBreakStyle:" + mHasLineBreakStyle + "\n" 4237 + " mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n" 4238 + " mLineBreakStyle:" + mLineBreakStyle + "\n" 4239 + " mLineBreakWordStyle:" + mLineBreakWordStyle + "\n" 4240 + "}"; 4241 } 4242 } 4243 4244 // Maps styleable attributes that exist both in TextView style and TextAppearance. 4245 private static final SparseIntArray sAppearanceValues = new SparseIntArray(); 4246 static { 4247 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, 4248 com.android.internal.R.styleable.TextAppearance_textColorHighlight); 4249 sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor, 4250 com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor); 4251 sAppearanceValues.put( 4252 com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor, 4253 com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor); 4254 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, 4255 com.android.internal.R.styleable.TextAppearance_textColor); 4256 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, 4257 com.android.internal.R.styleable.TextAppearance_textColorHint); 4258 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, 4259 com.android.internal.R.styleable.TextAppearance_textColorLink); 4260 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, 4261 com.android.internal.R.styleable.TextAppearance_textSize); 4262 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, 4263 com.android.internal.R.styleable.TextAppearance_textLocale); 4264 sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, 4265 com.android.internal.R.styleable.TextAppearance_typeface); 4266 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, 4267 com.android.internal.R.styleable.TextAppearance_fontFamily); 4268 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, 4269 com.android.internal.R.styleable.TextAppearance_textStyle); 4270 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, 4271 com.android.internal.R.styleable.TextAppearance_textFontWeight); 4272 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, 4273 com.android.internal.R.styleable.TextAppearance_textAllCaps); 4274 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, 4275 com.android.internal.R.styleable.TextAppearance_shadowColor); 4276 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, 4277 com.android.internal.R.styleable.TextAppearance_shadowDx); 4278 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, 4279 com.android.internal.R.styleable.TextAppearance_shadowDy); 4280 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, 4281 com.android.internal.R.styleable.TextAppearance_shadowRadius); 4282 sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, 4283 com.android.internal.R.styleable.TextAppearance_elegantTextHeight); 4284 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, 4285 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing); 4286 sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, 4287 com.android.internal.R.styleable.TextAppearance_letterSpacing); 4288 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, 4289 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings); 4290 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, 4291 com.android.internal.R.styleable.TextAppearance_fontVariationSettings); 4292 sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, 4293 com.android.internal.R.styleable.TextAppearance_lineBreakStyle); 4294 sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle, 4295 com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle); 4296 } 4297 4298 /** 4299 * Read the Text Appearance attributes from a given TypedArray and set its values to the given 4300 * set. If the TypedArray contains a value that was already set in the given attributes, that 4301 * will be overridden. 4302 * 4303 * @param context The Context to be used 4304 * @param appearance The TypedArray to read properties from 4305 * @param attributes the TextAppearanceAttributes to fill in 4306 * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines 4307 * what attribute indexes will be used to read the properties. 4308 */ 4309 private void readTextAppearance(Context context, TypedArray appearance, 4310 TextAppearanceAttributes attributes, boolean styleArray) { 4311 final int n = appearance.getIndexCount(); 4312 for (int i = 0; i < n; i++) { 4313 final int attr = appearance.getIndex(i); 4314 int index = attr; 4315 // Translate style array index ids to TextAppearance ids. 4316 if (styleArray) { 4317 index = sAppearanceValues.get(attr, -1); 4318 if (index == -1) { 4319 // This value is not part of a Text Appearance and should be ignored. 4320 continue; 4321 } 4322 } 4323 switch (index) { 4324 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 4325 attributes.mTextColorHighlight = 4326 appearance.getColor(attr, attributes.mTextColorHighlight); 4327 break; 4328 case com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor: 4329 attributes.mSearchResultHighlightColor = 4330 appearance.getColor(attr, attributes.mSearchResultHighlightColor); 4331 break; 4332 case com.android.internal.R.styleable 4333 .TextAppearance_focusedSearchResultHighlightColor: 4334 attributes.mFocusedSearchResultHighlightColor = 4335 appearance.getColor(attr, 4336 attributes.mFocusedSearchResultHighlightColor); 4337 break; 4338 case com.android.internal.R.styleable.TextAppearance_textColor: 4339 attributes.mTextColor = appearance.getColorStateList(attr); 4340 break; 4341 case com.android.internal.R.styleable.TextAppearance_textColorHint: 4342 attributes.mTextColorHint = appearance.getColorStateList(attr); 4343 break; 4344 case com.android.internal.R.styleable.TextAppearance_textColorLink: 4345 attributes.mTextColorLink = appearance.getColorStateList(attr); 4346 break; 4347 case com.android.internal.R.styleable.TextAppearance_textSize: 4348 attributes.mTextSize = 4349 appearance.getDimensionPixelSize(attr, attributes.mTextSize); 4350 attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit(); 4351 break; 4352 case com.android.internal.R.styleable.TextAppearance_textLocale: 4353 final String localeString = appearance.getString(attr); 4354 if (localeString != null) { 4355 final LocaleList localeList = LocaleList.forLanguageTags(localeString); 4356 if (!localeList.isEmpty()) { 4357 attributes.mTextLocales = localeList; 4358 } 4359 } 4360 break; 4361 case com.android.internal.R.styleable.TextAppearance_typeface: 4362 attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex); 4363 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4364 attributes.mFontFamily = null; 4365 } 4366 break; 4367 case com.android.internal.R.styleable.TextAppearance_fontFamily: 4368 if (!context.isRestricted() && context.canLoadUnsafeResources()) { 4369 try { 4370 attributes.mFontTypeface = appearance.getFont(attr); 4371 } catch (UnsupportedOperationException | Resources.NotFoundException e) { 4372 // Expected if it is not a font resource. 4373 } 4374 } 4375 if (attributes.mFontTypeface == null) { 4376 attributes.mFontFamily = appearance.getString(attr); 4377 } 4378 attributes.mFontFamilyExplicit = true; 4379 break; 4380 case com.android.internal.R.styleable.TextAppearance_textStyle: 4381 attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle); 4382 break; 4383 case com.android.internal.R.styleable.TextAppearance_textFontWeight: 4384 attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight); 4385 break; 4386 case com.android.internal.R.styleable.TextAppearance_textAllCaps: 4387 attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps); 4388 break; 4389 case com.android.internal.R.styleable.TextAppearance_shadowColor: 4390 attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor); 4391 break; 4392 case com.android.internal.R.styleable.TextAppearance_shadowDx: 4393 attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx); 4394 break; 4395 case com.android.internal.R.styleable.TextAppearance_shadowDy: 4396 attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy); 4397 break; 4398 case com.android.internal.R.styleable.TextAppearance_shadowRadius: 4399 attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius); 4400 break; 4401 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight: 4402 attributes.mHasElegant = true; 4403 attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant); 4404 break; 4405 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing: 4406 attributes.mHasFallbackLineSpacing = true; 4407 attributes.mFallbackLineSpacing = appearance.getBoolean(attr, 4408 attributes.mFallbackLineSpacing); 4409 break; 4410 case com.android.internal.R.styleable.TextAppearance_letterSpacing: 4411 attributes.mHasLetterSpacing = true; 4412 attributes.mLetterSpacing = 4413 appearance.getFloat(attr, attributes.mLetterSpacing); 4414 break; 4415 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings: 4416 attributes.mFontFeatureSettings = appearance.getString(attr); 4417 break; 4418 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings: 4419 attributes.mFontVariationSettings = appearance.getString(attr); 4420 break; 4421 case com.android.internal.R.styleable.TextAppearance_lineBreakStyle: 4422 attributes.mHasLineBreakStyle = true; 4423 attributes.mLineBreakStyle = 4424 appearance.getInt(attr, attributes.mLineBreakStyle); 4425 break; 4426 case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle: 4427 attributes.mHasLineBreakWordStyle = true; 4428 attributes.mLineBreakWordStyle = 4429 appearance.getInt(attr, attributes.mLineBreakWordStyle); 4430 break; 4431 default: 4432 } 4433 } 4434 } 4435 4436 private void applyTextAppearance(TextAppearanceAttributes attributes) { 4437 if (attributes.mTextColor != null) { 4438 setTextColor(attributes.mTextColor); 4439 } 4440 4441 if (attributes.mTextColorHint != null) { 4442 setHintTextColor(attributes.mTextColorHint); 4443 } 4444 4445 if (attributes.mTextColorLink != null) { 4446 setLinkTextColor(attributes.mTextColorLink); 4447 } 4448 4449 if (attributes.mTextColorHighlight != 0) { 4450 setHighlightColor(attributes.mTextColorHighlight); 4451 } 4452 4453 if (attributes.mSearchResultHighlightColor != 0) { 4454 setSearchResultHighlightColor(attributes.mSearchResultHighlightColor); 4455 } 4456 4457 if (attributes.mFocusedSearchResultHighlightColor != 0) { 4458 setFocusedSearchResultHighlightColor(attributes.mFocusedSearchResultHighlightColor); 4459 } 4460 4461 if (attributes.mTextSize != -1) { 4462 mTextSizeUnit = attributes.mTextSizeUnit; 4463 setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */); 4464 } 4465 4466 if (attributes.mTextLocales != null) { 4467 setTextLocales(attributes.mTextLocales); 4468 } 4469 4470 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4471 attributes.mFontFamily = null; 4472 } 4473 setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily, 4474 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight); 4475 4476 if (attributes.mShadowColor != 0) { 4477 setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy, 4478 attributes.mShadowColor); 4479 } 4480 4481 if (attributes.mAllCaps) { 4482 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 4483 } 4484 4485 if (attributes.mHasElegant) { 4486 setElegantTextHeight(attributes.mElegant); 4487 } 4488 4489 if (attributes.mHasFallbackLineSpacing) { 4490 setFallbackLineSpacing(attributes.mFallbackLineSpacing); 4491 } 4492 4493 if (attributes.mHasLetterSpacing) { 4494 setLetterSpacing(attributes.mLetterSpacing); 4495 } 4496 4497 if (attributes.mFontFeatureSettings != null) { 4498 setFontFeatureSettings(attributes.mFontFeatureSettings); 4499 } 4500 4501 if (attributes.mFontVariationSettings != null) { 4502 setFontVariationSettings(attributes.mFontVariationSettings); 4503 } 4504 4505 if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) { 4506 updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle, 4507 attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle, 4508 attributes.mLineBreakWordStyle); 4509 } 4510 } 4511 4512 /** 4513 * Updates the LineBreakConfig from the TextAppearance. 4514 * 4515 * This method updates the given line configuration from the TextAppearance. This method will 4516 * request new layout if line break config has been changed. 4517 * 4518 * @param isLineBreakStyleSpecified true if the line break style is specified. 4519 * @param isLineBreakWordStyleSpecified true if the line break word style is specified. 4520 * @param lineBreakStyle the value of the line break style in the TextAppearance. 4521 * @param lineBreakWordStyle the value of the line break word style in the TextAppearance. 4522 */ 4523 private void updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified, 4524 boolean isLineBreakWordStyleSpecified, 4525 @LineBreakConfig.LineBreakStyle int lineBreakStyle, 4526 @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { 4527 boolean updated = false; 4528 if (isLineBreakStyleSpecified && mLineBreakStyle != lineBreakStyle) { 4529 mLineBreakStyle = lineBreakStyle; 4530 updated = true; 4531 } 4532 if (isLineBreakWordStyleSpecified && mLineBreakWordStyle != lineBreakWordStyle) { 4533 mLineBreakWordStyle = lineBreakWordStyle; 4534 updated = true; 4535 } 4536 if (updated && mLayout != null) { 4537 nullLayouts(); 4538 requestLayout(); 4539 invalidate(); 4540 } 4541 } 4542 /** 4543 * Get the default primary {@link Locale} of the text in this TextView. This will always be 4544 * the first member of {@link #getTextLocales()}. 4545 * @return the default primary {@link Locale} of the text in this TextView. 4546 */ 4547 @NonNull 4548 public Locale getTextLocale() { 4549 return mTextPaint.getTextLocale(); 4550 } 4551 4552 /** 4553 * Get the default {@link LocaleList} of the text in this TextView. 4554 * @return the default {@link LocaleList} of the text in this TextView. 4555 */ 4556 @NonNull @Size(min = 1) 4557 public LocaleList getTextLocales() { 4558 return mTextPaint.getTextLocales(); 4559 } 4560 4561 private void changeListenerLocaleTo(@Nullable Locale locale) { 4562 if (mListenerChanged) { 4563 // If a listener has been explicitly set, don't change it. We may break something. 4564 return; 4565 } 4566 // The following null check is not absolutely necessary since all calling points of 4567 // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left 4568 // here in case others would want to call this method in the future. 4569 if (mEditor != null) { 4570 KeyListener listener = mEditor.mKeyListener; 4571 if (listener instanceof DigitsKeyListener) { 4572 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener); 4573 } else if (listener instanceof DateKeyListener) { 4574 listener = DateKeyListener.getInstance(locale); 4575 } else if (listener instanceof TimeKeyListener) { 4576 listener = TimeKeyListener.getInstance(locale); 4577 } else if (listener instanceof DateTimeKeyListener) { 4578 listener = DateTimeKeyListener.getInstance(locale); 4579 } else { 4580 return; 4581 } 4582 final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType); 4583 setKeyListenerOnly(listener); 4584 setInputTypeFromEditor(); 4585 if (wasPasswordType) { 4586 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS; 4587 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) { 4588 mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 4589 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) { 4590 mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD; 4591 } 4592 } 4593 } 4594 } 4595 4596 /** 4597 * Set the default {@link Locale} of the text in this TextView to a one-member 4598 * {@link LocaleList} containing just the given Locale. 4599 * 4600 * @param locale the {@link Locale} for drawing text, must not be null. 4601 * 4602 * @see #setTextLocales 4603 */ 4604 public void setTextLocale(@NonNull Locale locale) { 4605 mLocalesChanged = true; 4606 mTextPaint.setTextLocale(locale); 4607 if (mLayout != null) { 4608 nullLayouts(); 4609 requestLayout(); 4610 invalidate(); 4611 } 4612 } 4613 4614 /** 4615 * Set the default {@link LocaleList} of the text in this TextView to the given value. 4616 * 4617 * This value is used to choose appropriate typefaces for ambiguous characters (typically used 4618 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects 4619 * other aspects of text display, including line breaking. 4620 * 4621 * @param locales the {@link LocaleList} for drawing text, must not be null or empty. 4622 * 4623 * @see Paint#setTextLocales 4624 */ 4625 public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) { 4626 mLocalesChanged = true; 4627 mTextPaint.setTextLocales(locales); 4628 if (mLayout != null) { 4629 nullLayouts(); 4630 requestLayout(); 4631 invalidate(); 4632 } 4633 } 4634 4635 @Override 4636 protected void onConfigurationChanged(Configuration newConfig) { 4637 super.onConfigurationChanged(newConfig); 4638 if (!mLocalesChanged) { 4639 mTextPaint.setTextLocales(LocaleList.getDefault()); 4640 if (mLayout != null) { 4641 nullLayouts(); 4642 requestLayout(); 4643 invalidate(); 4644 } 4645 } 4646 if (mFontWeightAdjustment != newConfig.fontWeightAdjustment) { 4647 mFontWeightAdjustment = newConfig.fontWeightAdjustment; 4648 setTypeface(getTypeface()); 4649 } 4650 4651 InputMethodManager imm = getInputMethodManager(); 4652 // if orientation changed and this TextView is currently served. 4653 if (mLastOrientation != newConfig.orientation 4654 && imm != null && imm.hasActiveInputConnection(this)) { 4655 // EditorInfo.internalImeOptions are out of date. 4656 imm.restartInput(this); 4657 } 4658 mLastOrientation = newConfig.orientation; 4659 } 4660 4661 /** 4662 * @return the size (in pixels) of the default text size in this TextView. 4663 */ 4664 @InspectableProperty 4665 @ViewDebug.ExportedProperty(category = "text") 4666 public float getTextSize() { 4667 return mTextPaint.getTextSize(); 4668 } 4669 4670 /** 4671 * @return the size (in scaled pixels) of the default text size in this TextView. 4672 * @hide 4673 */ 4674 @ViewDebug.ExportedProperty(category = "text") 4675 public float getScaledTextSize() { 4676 return mTextPaint.getTextSize() / mTextPaint.density; 4677 } 4678 4679 /** @hide */ 4680 @ViewDebug.ExportedProperty(category = "text", mapping = { 4681 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"), 4682 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"), 4683 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"), 4684 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") 4685 }) 4686 public int getTypefaceStyle() { 4687 Typeface typeface = mTextPaint.getTypeface(); 4688 return typeface != null ? typeface.getStyle() : Typeface.NORMAL; 4689 } 4690 4691 /** 4692 * Set the default text size to the given value, interpreted as "scaled 4693 * pixel" units. This size is adjusted based on the current density and 4694 * user font size preference. 4695 * 4696 * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op. 4697 * 4698 * @param size The scaled pixel size. 4699 * 4700 * @attr ref android.R.styleable#TextView_textSize 4701 */ 4702 @android.view.RemotableViewMethod 4703 public void setTextSize(float size) { 4704 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 4705 } 4706 4707 /** 4708 * Set the default text size to a given unit and value. See {@link 4709 * TypedValue} for the possible dimension units. 4710 * 4711 * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op. 4712 * 4713 * @param unit The desired dimension unit. 4714 * @param size The desired size in the given units. 4715 * 4716 * @attr ref android.R.styleable#TextView_textSize 4717 */ 4718 public void setTextSize(int unit, float size) { 4719 if (!isAutoSizeEnabled()) { 4720 setTextSizeInternal(unit, size, true /* shouldRequestLayout */); 4721 } 4722 } 4723 4724 @NonNull 4725 private DisplayMetrics getDisplayMetricsOrSystem() { 4726 Context c = getContext(); 4727 Resources r; 4728 4729 if (c == null) { 4730 r = Resources.getSystem(); 4731 } else { 4732 r = c.getResources(); 4733 } 4734 4735 return r.getDisplayMetrics(); 4736 } 4737 4738 private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) { 4739 mTextSizeUnit = unit; 4740 setRawTextSize(TypedValue.applyDimension(unit, size, getDisplayMetricsOrSystem()), 4741 shouldRequestLayout); 4742 } 4743 4744 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 4745 private void setRawTextSize(float size, boolean shouldRequestLayout) { 4746 if (size != mTextPaint.getTextSize()) { 4747 mTextPaint.setTextSize(size); 4748 4749 maybeRecalculateLineHeight(); 4750 if (shouldRequestLayout && mLayout != null) { 4751 // Do not auto-size right after setting the text size. 4752 mNeedsAutoSizeText = false; 4753 nullLayouts(); 4754 requestLayout(); 4755 invalidate(); 4756 } 4757 } 4758 } 4759 4760 /** 4761 * Gets the text size unit defined by the developer. It may be specified in resources or be 4762 * passed as the unit argument of {@link #setTextSize(int, float)} at runtime. 4763 * 4764 * @return the dimension type of the text size unit originally defined. 4765 * @see TypedValue#TYPE_DIMENSION 4766 */ 4767 public int getTextSizeUnit() { 4768 return mTextSizeUnit; 4769 } 4770 4771 /** 4772 * Gets the extent by which text should be stretched horizontally. 4773 * This will usually be 1.0. 4774 * @return The horizontal scale factor. 4775 */ 4776 @InspectableProperty 4777 public float getTextScaleX() { 4778 return mTextPaint.getTextScaleX(); 4779 } 4780 4781 /** 4782 * Sets the horizontal scale factor for text. The default value 4783 * is 1.0. Values greater than 1.0 stretch the text wider. 4784 * Values less than 1.0 make the text narrower. By default, this value is 1.0. 4785 * @param size The horizontal scale factor. 4786 * @attr ref android.R.styleable#TextView_textScaleX 4787 */ 4788 @android.view.RemotableViewMethod 4789 public void setTextScaleX(float size) { 4790 if (size != mTextPaint.getTextScaleX()) { 4791 mUserSetTextScaleX = true; 4792 mTextPaint.setTextScaleX(size); 4793 4794 if (mLayout != null) { 4795 nullLayouts(); 4796 requestLayout(); 4797 invalidate(); 4798 } 4799 } 4800 } 4801 4802 /** 4803 * Sets the typeface and style in which the text should be displayed. 4804 * Note that not all Typeface families actually have bold and italic 4805 * variants, so you may need to use 4806 * {@link #setTypeface(Typeface, int)} to get the appearance 4807 * that you actually want. 4808 * 4809 * @see #getTypeface() 4810 * 4811 * @attr ref android.R.styleable#TextView_fontFamily 4812 * @attr ref android.R.styleable#TextView_typeface 4813 * @attr ref android.R.styleable#TextView_textStyle 4814 */ 4815 public void setTypeface(@Nullable Typeface tf) { 4816 mOriginalTypeface = tf; 4817 if (mFontWeightAdjustment != 0 4818 && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) { 4819 if (tf == null) { 4820 if (Flags.fixNullTypefaceBolding()) { 4821 tf = Typeface.DEFAULT_BOLD; 4822 } else { 4823 tf = Typeface.DEFAULT; 4824 } 4825 } else { 4826 int newWeight = Math.min( 4827 Math.max(tf.getWeight() + mFontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN), 4828 FontStyle.FONT_WEIGHT_MAX); 4829 int typefaceStyle = tf != null ? tf.getStyle() : 0; 4830 boolean italic = (typefaceStyle & Typeface.ITALIC) != 0; 4831 tf = Typeface.create(tf, newWeight, italic); 4832 } 4833 } 4834 if (mTextPaint.getTypeface() != tf) { 4835 mTextPaint.setTypeface(tf); 4836 4837 if (mLayout != null) { 4838 nullLayouts(); 4839 requestLayout(); 4840 invalidate(); 4841 } 4842 } 4843 } 4844 4845 /** 4846 * Gets the current {@link Typeface} that is used to style the text. 4847 * @return The current Typeface. 4848 * 4849 * @see #setTypeface(Typeface) 4850 * 4851 * @attr ref android.R.styleable#TextView_fontFamily 4852 * @attr ref android.R.styleable#TextView_typeface 4853 * @attr ref android.R.styleable#TextView_textStyle 4854 */ 4855 @InspectableProperty 4856 public Typeface getTypeface() { 4857 return mOriginalTypeface; 4858 } 4859 4860 /** 4861 * Set the TextView's elegant height metrics flag. This setting selects font 4862 * variants that have not been compacted to fit Latin-based vertical 4863 * metrics, and also increases top and bottom bounds to provide more space. 4864 * 4865 * @param elegant set the paint's elegant metrics flag. 4866 * 4867 * @see #isElegantTextHeight() 4868 * @see Paint#isElegantTextHeight() 4869 * 4870 * @attr ref android.R.styleable#TextView_elegantTextHeight 4871 */ 4872 public void setElegantTextHeight(boolean elegant) { 4873 if (elegant != mTextPaint.isElegantTextHeight()) { 4874 mTextPaint.setElegantTextHeight(elegant); 4875 if (mLayout != null) { 4876 nullLayouts(); 4877 requestLayout(); 4878 invalidate(); 4879 } 4880 } 4881 } 4882 4883 /** 4884 * Set whether to respect the ascent and descent of the fallback fonts that are used in 4885 * displaying the text (which is needed to avoid text from consecutive lines running into 4886 * each other). If set, fallback fonts that end up getting used can increase the ascent 4887 * and descent of the lines that they are used on. 4888 * <p/> 4889 * It is required to be true if text could be in languages like Burmese or Tibetan where text 4890 * is typically much taller or deeper than Latin text. 4891 * 4892 * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default 4893 * 4894 * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean) 4895 * 4896 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4897 */ 4898 public void setFallbackLineSpacing(boolean enabled) { 4899 int fallbackStrategy; 4900 if (enabled) { 4901 if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) { 4902 fallbackStrategy = FALLBACK_LINE_SPACING_ALL; 4903 } else { 4904 fallbackStrategy = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; 4905 } 4906 } else { 4907 fallbackStrategy = FALLBACK_LINE_SPACING_NONE; 4908 } 4909 if (mUseFallbackLineSpacing != fallbackStrategy) { 4910 mUseFallbackLineSpacing = fallbackStrategy; 4911 if (mLayout != null) { 4912 nullLayouts(); 4913 requestLayout(); 4914 invalidate(); 4915 } 4916 } 4917 } 4918 4919 /** 4920 * Set true for using width of bounding box as a source of automatic line breaking and drawing. 4921 * 4922 * If this value is false, the TextView determines the View width, drawing offset and automatic 4923 * line breaking based on total advances as text widths. By setting true, use glyph bound's as a 4924 * source of text width. 4925 * 4926 * If the font used for this TextView has glyphs that has negative bearing X or glyph xMax is 4927 * greater than advance, the glyph clipping can be happened because the drawing area may be 4928 * bigger than advance. By setting this to true, the TextView will reserve more spaces for 4929 * drawing are, so clipping can be prevented. 4930 * 4931 * This value is true by default if the target API version is 35 or later. 4932 * 4933 * @param useBoundsForWidth true for using bounding box for width. false for using advances for 4934 * width. 4935 * @see #getUseBoundsForWidth() 4936 * @see #setShiftDrawingOffsetForStartOverhang(boolean) 4937 * @see #getShiftDrawingOffsetForStartOverhang() 4938 */ 4939 @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) 4940 public void setUseBoundsForWidth(boolean useBoundsForWidth) { 4941 if (mUseBoundsForWidth != useBoundsForWidth) { 4942 mUseBoundsForWidth = useBoundsForWidth; 4943 if (mLayout != null) { 4944 nullLayouts(); 4945 requestLayout(); 4946 invalidate(); 4947 } 4948 } 4949 } 4950 4951 /** 4952 * Returns true if using bounding box as a width, false for using advance as a width. 4953 * 4954 * @see #setUseBoundsForWidth(boolean) 4955 * @see #setShiftDrawingOffsetForStartOverhang(boolean) 4956 * @see #getShiftDrawingOffsetForStartOverhang() 4957 * @return True if using bounding box for width, false if using advance for width. 4958 */ 4959 @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) 4960 public boolean getUseBoundsForWidth() { 4961 return mUseBoundsForWidth; 4962 } 4963 4964 /** 4965 * Set true for shifting the drawing x offset for showing overhang at the start position. 4966 * 4967 * This flag is ignored if the {@link #getUseBoundsForWidth()} is false. 4968 * 4969 * If this value is false, the TextView draws text from the zero even if there is a glyph stroke 4970 * in a region where the x coordinate is negative. TextView clips the stroke in the region where 4971 * the X coordinate is negative unless the parents has {@link ViewGroup#getClipChildren()} to 4972 * true. This is useful for aligning multiple TextViews vertically. 4973 * 4974 * If this value is true, the TextView draws text with shifting the x coordinate of the drawing 4975 * bounding box. This prevents the clipping even if the parents doesn't have 4976 * {@link ViewGroup#getClipChildren()} to true. 4977 * 4978 * This value is false by default. 4979 * 4980 * @param shiftDrawingOffsetForStartOverhang true for shifting the drawing offset for showing 4981 * the stroke that is in the region whre the x 4982 * coorinate is negative. 4983 * @see #setUseBoundsForWidth(boolean) 4984 * @see #getUseBoundsForWidth() 4985 */ 4986 @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) 4987 public void setShiftDrawingOffsetForStartOverhang(boolean shiftDrawingOffsetForStartOverhang) { 4988 if (mShiftDrawingOffsetForStartOverhang != shiftDrawingOffsetForStartOverhang) { 4989 mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang; 4990 if (mLayout != null) { 4991 nullLayouts(); 4992 requestLayout(); 4993 invalidate(); 4994 } 4995 } 4996 } 4997 4998 /** 4999 * Returns true if shifting the drawing x offset for start overhang. 5000 * 5001 * @see #setShiftDrawingOffsetForStartOverhang(boolean) 5002 * @see #setUseBoundsForWidth(boolean) 5003 * @see #getUseBoundsForWidth() 5004 * @return True if shifting the drawing x offset for start overhang. 5005 */ 5006 @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) 5007 public boolean getShiftDrawingOffsetForStartOverhang() { 5008 return mShiftDrawingOffsetForStartOverhang; 5009 } 5010 5011 /** 5012 * Set the minimum font metrics used for line spacing. 5013 * 5014 * <p> 5015 * {@code null} is the default value. If {@code null} is set or left as default, the font 5016 * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used. 5017 * 5018 * <p> 5019 * The minimum meaning here is the minimum value of line spacing: maximum value of 5020 * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}. 5021 * 5022 * <p> 5023 * By setting this value, each line will have minimum line spacing regardless of the text 5024 * rendered. For example, usually Japanese script has larger vertical metrics than Latin script. 5025 * By setting the metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} 5026 * for Japanese or leave it {@code null} if the TextView's locale or system locale is Japanese, 5027 * the line spacing for Japanese is reserved if the TextView contains English text. If the 5028 * vertical metrics of the text is larger than Japanese, for example Burmese, the bigger font 5029 * metrics is used. 5030 * 5031 * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the value 5032 * obtained by 5033 * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} 5034 * @see #getMinimumFontMetrics() 5035 * @see Layout#getMinimumFontMetrics() 5036 * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics) 5037 * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics) 5038 * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics) 5039 */ 5040 @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) 5041 public void setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) { 5042 mMinimumFontMetrics = minimumFontMetrics; 5043 } 5044 5045 /** 5046 * Get the minimum font metrics used for line spacing. 5047 * 5048 * @see #setMinimumFontMetrics(Paint.FontMetrics) 5049 * @see Layout#getMinimumFontMetrics() 5050 * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics) 5051 * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics) 5052 * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics) 5053 * 5054 * @return a minimum font metrics. {@code null} for using the value obtained by 5055 * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} 5056 */ 5057 @Nullable 5058 @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) 5059 public Paint.FontMetrics getMinimumFontMetrics() { 5060 return mMinimumFontMetrics; 5061 } 5062 5063 /** 5064 * Returns true if the locale preferred line height is used for the minimum line height. 5065 * 5066 * @return true if using locale preferred line height for the minimum line height. Otherwise 5067 * false. 5068 * 5069 * @see #setLocalePreferredLineHeightForMinimumUsed(boolean) 5070 * @see #setMinimumFontMetrics(Paint.FontMetrics) 5071 * @see #getMinimumFontMetrics() 5072 */ 5073 @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) 5074 public boolean isLocalePreferredLineHeightForMinimumUsed() { 5075 return mUseLocalePreferredLineHeightForMinimum; 5076 } 5077 5078 /** 5079 * Set true if the locale preferred line height is used for the minimum line height. 5080 * 5081 * By setting this flag to true is equivalenet to call 5082 * {@link #setMinimumFontMetrics(Paint.FontMetrics)} with the one obtained by 5083 * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}. 5084 * 5085 * If custom minimum line height was specified by 5086 * {@link #setMinimumFontMetrics(Paint.FontMetrics)}, this flag will be ignored. 5087 * 5088 * @param flag true for using locale preferred line height for the minimum line height. 5089 * @see #isLocalePreferredLineHeightForMinimumUsed() 5090 * @see #setMinimumFontMetrics(Paint.FontMetrics) 5091 * @see #getMinimumFontMetrics() 5092 */ 5093 @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) 5094 public void setLocalePreferredLineHeightForMinimumUsed(boolean flag) { 5095 mUseLocalePreferredLineHeightForMinimum = flag; 5096 } 5097 5098 /** 5099 * @return whether fallback line spacing is enabled, {@code true} by default 5100 * 5101 * @see #setFallbackLineSpacing(boolean) 5102 * 5103 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 5104 */ 5105 @InspectableProperty 5106 public boolean isFallbackLineSpacing() { 5107 return mUseFallbackLineSpacing != FALLBACK_LINE_SPACING_NONE; 5108 } 5109 5110 private boolean isFallbackLineSpacingForBoringLayout() { 5111 return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL; 5112 } 5113 5114 // Package privte for accessing from Editor.java 5115 /* package */ boolean isFallbackLineSpacingForStaticLayout() { 5116 return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL 5117 || mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; 5118 } 5119 5120 /** 5121 * Get the value of the TextView's elegant height metrics flag. This setting selects font 5122 * variants that have not been compacted to fit Latin-based vertical 5123 * metrics, and also increases top and bottom bounds to provide more space. 5124 * @return {@code true} if the elegant height metrics flag is set. 5125 * 5126 * @see #setElegantTextHeight(boolean) 5127 * @see Paint#setElegantTextHeight(boolean) 5128 */ 5129 @InspectableProperty 5130 public boolean isElegantTextHeight() { 5131 return mTextPaint.isElegantTextHeight(); 5132 } 5133 5134 /** 5135 * Gets the text letter-space value, which determines the spacing between characters. 5136 * The value returned is in ems. Normally, this value is 0.0. 5137 * @return The text letter-space value in ems. 5138 * 5139 * @see #setLetterSpacing(float) 5140 * @see Paint#setLetterSpacing 5141 */ 5142 @InspectableProperty 5143 public float getLetterSpacing() { 5144 return mTextPaint.getLetterSpacing(); 5145 } 5146 5147 /** 5148 * Sets text letter-spacing in em units. Typical values 5149 * for slight expansion will be around 0.05. Negative values tighten text. 5150 * 5151 * @see #getLetterSpacing() 5152 * @see Paint#getLetterSpacing 5153 * 5154 * @param letterSpacing A text letter-space value in ems. 5155 * @attr ref android.R.styleable#TextView_letterSpacing 5156 */ 5157 @android.view.RemotableViewMethod 5158 public void setLetterSpacing(float letterSpacing) { 5159 if (letterSpacing != mTextPaint.getLetterSpacing()) { 5160 mTextPaint.setLetterSpacing(letterSpacing); 5161 5162 if (mLayout != null) { 5163 nullLayouts(); 5164 requestLayout(); 5165 invalidate(); 5166 } 5167 } 5168 } 5169 5170 /** 5171 * Returns the font feature settings. The format is the same as the CSS 5172 * font-feature-settings attribute: 5173 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 5174 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 5175 * 5176 * @return the currently set font feature settings. Default is null. 5177 * 5178 * @see #setFontFeatureSettings(String) 5179 * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String) 5180 */ 5181 @InspectableProperty 5182 @Nullable 5183 public String getFontFeatureSettings() { 5184 return mTextPaint.getFontFeatureSettings(); 5185 } 5186 5187 /** 5188 * Returns the font variation settings. 5189 * 5190 * @return the currently set font variation settings. Returns null if no variation is 5191 * specified. 5192 * 5193 * @see #setFontVariationSettings(String) 5194 * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String) 5195 */ 5196 @Nullable 5197 public String getFontVariationSettings() { 5198 return mTextPaint.getFontVariationSettings(); 5199 } 5200 5201 /** 5202 * Sets the break strategy for breaking paragraphs into lines. The default value for 5203 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for 5204 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the 5205 * text "dancing" when being edited. 5206 * <p> 5207 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 5208 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 5209 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 5210 * improves the structure of text layout however has performance impact and requires more time 5211 * to do the text layout.</p> 5212 * <p> 5213 * Compared with {@link #setLineBreakStyle(int)}, line break style with different strictness is 5214 * evaluated in the ICU to identify the potential breakpoints. In 5215 * {@link #setBreakStrategy(int)}, line break strategy handles the post processing of ICU's line 5216 * break result. It aims to evaluate ICU's breakpoints and break the lines based on the 5217 * constraint. 5218 * </p> 5219 * 5220 * @attr ref android.R.styleable#TextView_breakStrategy 5221 * @see #getBreakStrategy() 5222 * @see #setHyphenationFrequency(int) 5223 */ 5224 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) { 5225 mBreakStrategy = breakStrategy; 5226 if (mLayout != null) { 5227 nullLayouts(); 5228 requestLayout(); 5229 invalidate(); 5230 } 5231 } 5232 5233 /** 5234 * Gets the current strategy for breaking paragraphs into lines. 5235 * @return the current strategy for breaking paragraphs into lines. 5236 * 5237 * @attr ref android.R.styleable#TextView_breakStrategy 5238 * @see #setBreakStrategy(int) 5239 */ 5240 @InspectableProperty(enumMapping = { 5241 @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE), 5242 @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY), 5243 @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED) 5244 }) 5245 @Layout.BreakStrategy 5246 public int getBreakStrategy() { 5247 return mBreakStrategy; 5248 } 5249 5250 /** 5251 * Sets the frequency of automatic hyphenation to use when determining word breaks. 5252 * The default value for both TextView and {@link EditText} is 5253 * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value 5254 * is set from the theme. 5255 * <p/> 5256 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 5257 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 5258 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 5259 * improves the structure of text layout however has performance impact and requires more time 5260 * to do the text layout. 5261 * <p/> 5262 * Note: Before Android Q, in the theme hyphenation frequency is set to 5263 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into 5264 * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q. 5265 * 5266 * @param hyphenationFrequency the hyphenation frequency to use, one of 5267 * {@link Layout#HYPHENATION_FREQUENCY_NONE}, 5268 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}, 5269 * {@link Layout#HYPHENATION_FREQUENCY_FULL} 5270 * @attr ref android.R.styleable#TextView_hyphenationFrequency 5271 * @see #getHyphenationFrequency() 5272 * @see #getBreakStrategy() 5273 */ 5274 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) { 5275 mHyphenationFrequency = hyphenationFrequency; 5276 if (mLayout != null) { 5277 nullLayouts(); 5278 requestLayout(); 5279 invalidate(); 5280 } 5281 } 5282 5283 /** 5284 * Gets the current frequency of automatic hyphenation to be used when determining word breaks. 5285 * @return the current frequency of automatic hyphenation to be used when determining word 5286 * breaks. 5287 * 5288 * @attr ref android.R.styleable#TextView_hyphenationFrequency 5289 * @see #setHyphenationFrequency(int) 5290 */ 5291 @InspectableProperty(enumMapping = { 5292 @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE), 5293 @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL), 5294 @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL) 5295 }) 5296 @Layout.HyphenationFrequency 5297 public int getHyphenationFrequency() { 5298 return mHyphenationFrequency; 5299 } 5300 5301 /** 5302 * Sets the line-break style for text wrapping. 5303 * 5304 * <p>Line-break style specifies the line-break strategies that can be used 5305 * for text wrapping. The line-break style affects rule-based line breaking 5306 * by specifying the strictness of line-breaking rules. 5307 * 5308 * <p>The following are types of line-break styles: 5309 * <ul> 5310 * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE} 5311 * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} 5312 * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT} 5313 * </ul> 5314 * 5315 * <p>The default line-break style is 5316 * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no 5317 * line-breaking rules are used. 5318 * 5319 * <p>See the 5320 * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external"> 5321 * line-break property</a> for more information. 5322 * 5323 * @param lineBreakStyle The line-break style for the text. 5324 */ 5325 public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) { 5326 if (mLineBreakStyle != lineBreakStyle) { 5327 mLineBreakStyle = lineBreakStyle; 5328 if (mLayout != null) { 5329 nullLayouts(); 5330 requestLayout(); 5331 invalidate(); 5332 } 5333 } 5334 } 5335 5336 /** 5337 * Sets the line-break word style for text wrapping. 5338 * 5339 * <p>The line-break word style affects dictionary-based line breaking by 5340 * providing phrase-based line-breaking opportunities. Use 5341 * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify 5342 * phrase-based line breaking. 5343 * 5344 * <p>The default line-break word style is 5345 * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that 5346 * no line-breaking word style is used. 5347 * 5348 * <p>See the 5349 * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external"> 5350 * word-break property</a> for more information. 5351 * 5352 * @param lineBreakWordStyle The line-break word style for the text. 5353 */ 5354 public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { 5355 if (mLineBreakWordStyle != lineBreakWordStyle) { 5356 mLineBreakWordStyle = lineBreakWordStyle; 5357 if (mLayout != null) { 5358 nullLayouts(); 5359 requestLayout(); 5360 invalidate(); 5361 } 5362 } 5363 } 5364 5365 /** 5366 * Gets the current line-break style for text wrapping. 5367 * 5368 * @return The line-break style to be used for text wrapping. 5369 */ 5370 public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() { 5371 return mLineBreakStyle; 5372 } 5373 5374 /** 5375 * Gets the current line-break word style for text wrapping. 5376 * 5377 * @return The line-break word style to be used for text wrapping. 5378 */ 5379 public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() { 5380 return mLineBreakWordStyle; 5381 } 5382 5383 /** 5384 * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}. 5385 * 5386 * @return a current {@link PrecomputedText.Params} 5387 * @see PrecomputedText 5388 */ 5389 public @NonNull PrecomputedText.Params getTextMetricsParams() { 5390 return new PrecomputedText.Params(new TextPaint(mTextPaint), 5391 LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle), 5392 getTextDirectionHeuristic(), 5393 mBreakStrategy, mHyphenationFrequency); 5394 } 5395 5396 /** 5397 * Apply the text layout parameter. 5398 * 5399 * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}. 5400 * @see PrecomputedText 5401 */ 5402 public void setTextMetricsParams(@NonNull PrecomputedText.Params params) { 5403 mTextPaint.set(params.getTextPaint()); 5404 mUserSetTextScaleX = true; 5405 mTextDir = params.getTextDirection(); 5406 mBreakStrategy = params.getBreakStrategy(); 5407 mHyphenationFrequency = params.getHyphenationFrequency(); 5408 LineBreakConfig lineBreakConfig = params.getLineBreakConfig(); 5409 mLineBreakStyle = LineBreakConfig.getResolvedLineBreakStyle(lineBreakConfig); 5410 mLineBreakWordStyle = LineBreakConfig.getResolvedLineBreakWordStyle(lineBreakConfig); 5411 if (mLayout != null) { 5412 nullLayouts(); 5413 requestLayout(); 5414 invalidate(); 5415 } 5416 } 5417 5418 /** 5419 * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the 5420 * last line is too short for justification, the last line will be displayed with the 5421 * alignment set by {@link android.view.View#setTextAlignment}. 5422 * 5423 * @see #getJustificationMode() 5424 */ 5425 @Layout.JustificationMode 5426 @android.view.RemotableViewMethod 5427 public void setJustificationMode(@Layout.JustificationMode int justificationMode) { 5428 mJustificationMode = justificationMode; 5429 if (mLayout != null) { 5430 nullLayouts(); 5431 requestLayout(); 5432 invalidate(); 5433 } 5434 } 5435 5436 /** 5437 * @return true if currently paragraph justification mode. 5438 * 5439 * @see #setJustificationMode(int) 5440 */ 5441 @InspectableProperty(enumMapping = { 5442 @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE), 5443 @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD) 5444 }) 5445 public @Layout.JustificationMode int getJustificationMode() { 5446 return mJustificationMode; 5447 } 5448 5449 /** 5450 * Sets font feature settings. The format is the same as the CSS 5451 * font-feature-settings attribute: 5452 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 5453 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 5454 * 5455 * @param fontFeatureSettings font feature settings represented as CSS compatible string 5456 * 5457 * @see #getFontFeatureSettings() 5458 * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings() 5459 * 5460 * @attr ref android.R.styleable#TextView_fontFeatureSettings 5461 */ 5462 @android.view.RemotableViewMethod 5463 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) { 5464 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) { 5465 mTextPaint.setFontFeatureSettings(fontFeatureSettings); 5466 5467 if (mLayout != null) { 5468 nullLayouts(); 5469 requestLayout(); 5470 invalidate(); 5471 } 5472 } 5473 } 5474 5475 5476 /** 5477 * Sets TrueType or OpenType font variation settings. The settings string is constructed from 5478 * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters 5479 * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that 5480 * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E 5481 * are invalid. If a specified axis name is not defined in the font, the settings will be 5482 * ignored. 5483 * 5484 * <p> 5485 * Examples, 5486 * <ul> 5487 * <li>Set font width to 150. 5488 * <pre> 5489 * <code> 5490 * TextView textView = (TextView) findViewById(R.id.textView); 5491 * textView.setFontVariationSettings("'wdth' 150"); 5492 * </code> 5493 * </pre> 5494 * </li> 5495 * 5496 * <li>Set the font slant to 20 degrees and ask for italic style. 5497 * <pre> 5498 * <code> 5499 * TextView textView = (TextView) findViewById(R.id.textView); 5500 * textView.setFontVariationSettings("'slnt' 20, 'ital' 1"); 5501 * </code> 5502 * </pre> 5503 * </p> 5504 * </li> 5505 * </ul> 5506 * 5507 * @param fontVariationSettings font variation settings. You can pass null or empty string as 5508 * no variation settings. 5509 * @return true if the given settings is effective to at least one font file underlying this 5510 * TextView. This function also returns true for empty settings string. Otherwise 5511 * returns false. 5512 * 5513 * @throws IllegalArgumentException If given string is not a valid font variation settings 5514 * format. 5515 * 5516 * @see #getFontVariationSettings() 5517 * @see FontVariationAxis 5518 * 5519 * @attr ref android.R.styleable#TextView_fontVariationSettings 5520 */ 5521 @android.view.RemotableViewMethod 5522 public boolean setFontVariationSettings(@Nullable String fontVariationSettings) { 5523 final String existingSettings = mTextPaint.getFontVariationSettings(); 5524 if (fontVariationSettings == existingSettings 5525 || (fontVariationSettings != null 5526 && fontVariationSettings.equals(existingSettings))) { 5527 return true; 5528 } 5529 boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings); 5530 5531 if (effective && mLayout != null) { 5532 nullLayouts(); 5533 requestLayout(); 5534 invalidate(); 5535 } 5536 return effective; 5537 } 5538 5539 /** 5540 * Sets the text color for all the states (normal, selected, 5541 * focused) to be this color. 5542 * 5543 * @param color A color value in the form 0xAARRGGBB. 5544 * Do not pass a resource ID. To get a color value from a resource ID, call 5545 * {@link androidx.core.content.ContextCompat#getColor(Context, int) getColor}. 5546 * 5547 * @see #setTextColor(ColorStateList) 5548 * @see #getTextColors() 5549 * 5550 * @attr ref android.R.styleable#TextView_textColor 5551 */ 5552 @android.view.RemotableViewMethod 5553 public void setTextColor(@ColorInt int color) { 5554 mTextColor = ColorStateList.valueOf(color); 5555 updateTextColors(); 5556 } 5557 5558 /** 5559 * Sets the text color. 5560 * 5561 * @see #setTextColor(int) 5562 * @see #getTextColors() 5563 * @see #setHintTextColor(ColorStateList) 5564 * @see #setLinkTextColor(ColorStateList) 5565 * 5566 * @attr ref android.R.styleable#TextView_textColor 5567 */ 5568 @android.view.RemotableViewMethod 5569 public void setTextColor(ColorStateList colors) { 5570 if (colors == null) { 5571 throw new NullPointerException(); 5572 } 5573 5574 mTextColor = colors; 5575 updateTextColors(); 5576 } 5577 5578 /** 5579 * Gets the text colors for the different states (normal, selected, focused) of the TextView. 5580 * 5581 * @see #setTextColor(ColorStateList) 5582 * @see #setTextColor(int) 5583 * 5584 * @attr ref android.R.styleable#TextView_textColor 5585 */ 5586 @InspectableProperty(name = "textColor") 5587 public final ColorStateList getTextColors() { 5588 return mTextColor; 5589 } 5590 5591 /** 5592 * Return the current color selected for normal text. 5593 * 5594 * @return Returns the current text color. 5595 */ 5596 @ColorInt 5597 public final int getCurrentTextColor() { 5598 return mCurTextColor; 5599 } 5600 5601 /** 5602 * Sets the color used to display the selection highlight. 5603 * 5604 * @attr ref android.R.styleable#TextView_textColorHighlight 5605 */ 5606 @android.view.RemotableViewMethod 5607 public void setHighlightColor(@ColorInt int color) { 5608 if (mHighlightColor != color) { 5609 mHighlightColor = color; 5610 invalidate(); 5611 } 5612 } 5613 5614 /** 5615 * @return the color used to display the selection highlight 5616 * 5617 * @see #setHighlightColor(int) 5618 * 5619 * @attr ref android.R.styleable#TextView_textColorHighlight 5620 */ 5621 @InspectableProperty(name = "textColorHighlight") 5622 @ColorInt 5623 public int getHighlightColor() { 5624 return mHighlightColor; 5625 } 5626 5627 /** 5628 * Sets whether the soft input method will be made visible when this 5629 * TextView gets focused. The default is true. 5630 */ 5631 @android.view.RemotableViewMethod 5632 public final void setShowSoftInputOnFocus(boolean show) { 5633 createEditorIfNeeded(); 5634 mEditor.mShowSoftInputOnFocus = show; 5635 } 5636 5637 /** 5638 * Returns whether the soft input method will be made visible when this 5639 * TextView gets focused. The default is true. 5640 */ 5641 public final boolean getShowSoftInputOnFocus() { 5642 // When there is no Editor, return default true value 5643 return mEditor == null || mEditor.mShowSoftInputOnFocus; 5644 } 5645 5646 /** 5647 * Gives the text a shadow of the specified blur radius and color, the specified 5648 * distance from its drawn position. 5649 * <p> 5650 * The text shadow produced does not interact with the properties on view 5651 * that are responsible for real time shadows, 5652 * {@link View#getElevation() elevation} and 5653 * {@link View#getTranslationZ() translationZ}. 5654 * 5655 * @see Paint#setShadowLayer(float, float, float, int) 5656 * 5657 * @attr ref android.R.styleable#TextView_shadowColor 5658 * @attr ref android.R.styleable#TextView_shadowDx 5659 * @attr ref android.R.styleable#TextView_shadowDy 5660 * @attr ref android.R.styleable#TextView_shadowRadius 5661 */ 5662 public void setShadowLayer(float radius, float dx, float dy, int color) { 5663 mTextPaint.setShadowLayer(radius, dx, dy, color); 5664 5665 mShadowRadius = radius; 5666 mShadowDx = dx; 5667 mShadowDy = dy; 5668 mShadowColor = color; 5669 5670 // Will change text clip region 5671 if (mEditor != null) { 5672 mEditor.invalidateTextDisplayList(); 5673 mEditor.invalidateHandlesAndActionMode(); 5674 } 5675 invalidate(); 5676 } 5677 5678 /** 5679 * Gets the radius of the shadow layer. 5680 * 5681 * @return the radius of the shadow layer. If 0, the shadow layer is not visible 5682 * 5683 * @see #setShadowLayer(float, float, float, int) 5684 * 5685 * @attr ref android.R.styleable#TextView_shadowRadius 5686 */ 5687 @InspectableProperty 5688 public float getShadowRadius() { 5689 return mShadowRadius; 5690 } 5691 5692 /** 5693 * @return the horizontal offset of the shadow layer 5694 * 5695 * @see #setShadowLayer(float, float, float, int) 5696 * 5697 * @attr ref android.R.styleable#TextView_shadowDx 5698 */ 5699 @InspectableProperty 5700 public float getShadowDx() { 5701 return mShadowDx; 5702 } 5703 5704 /** 5705 * Gets the vertical offset of the shadow layer. 5706 * @return The vertical offset of the shadow layer. 5707 * 5708 * @see #setShadowLayer(float, float, float, int) 5709 * 5710 * @attr ref android.R.styleable#TextView_shadowDy 5711 */ 5712 @InspectableProperty 5713 public float getShadowDy() { 5714 return mShadowDy; 5715 } 5716 5717 /** 5718 * Gets the color of the shadow layer. 5719 * @return the color of the shadow layer 5720 * 5721 * @see #setShadowLayer(float, float, float, int) 5722 * 5723 * @attr ref android.R.styleable#TextView_shadowColor 5724 */ 5725 @InspectableProperty 5726 @ColorInt 5727 public int getShadowColor() { 5728 return mShadowColor; 5729 } 5730 5731 /** 5732 * Gets the {@link TextPaint} used for the text. 5733 * Use this only to consult the Paint's properties and not to change them. 5734 * @return The base paint used for the text. 5735 */ 5736 public TextPaint getPaint() { 5737 return mTextPaint; 5738 } 5739 5740 /** 5741 * Sets the autolink mask of the text. See {@link 5742 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 5743 * possible values. 5744 * 5745 * <p class="note"><b>Note:</b> 5746 * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES} 5747 * is deprecated and should be avoided; see its documentation. 5748 * 5749 * @attr ref android.R.styleable#TextView_autoLink 5750 */ 5751 @android.view.RemotableViewMethod 5752 public final void setAutoLinkMask(int mask) { 5753 mAutoLinkMask = mask; 5754 } 5755 5756 /** 5757 * Sets whether the movement method will automatically be set to 5758 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 5759 * set to nonzero and links are detected in {@link #setText}. 5760 * The default is true. 5761 * 5762 * @attr ref android.R.styleable#TextView_linksClickable 5763 */ 5764 @android.view.RemotableViewMethod 5765 public final void setLinksClickable(boolean whether) { 5766 mLinksClickable = whether; 5767 } 5768 5769 /** 5770 * Returns whether the movement method will automatically be set to 5771 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 5772 * set to nonzero and links are detected in {@link #setText}. 5773 * The default is true. 5774 * 5775 * @attr ref android.R.styleable#TextView_linksClickable 5776 */ 5777 @InspectableProperty 5778 public final boolean getLinksClickable() { 5779 return mLinksClickable; 5780 } 5781 5782 /** 5783 * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text 5784 * (by {@link Linkify} or otherwise) if any. You can call 5785 * {@link URLSpan#getURL} on them to find where they link to 5786 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 5787 * to find the region of the text they are attached to. 5788 */ 5789 public URLSpan[] getUrls() { 5790 if (mText instanceof Spanned) { 5791 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 5792 } else { 5793 return new URLSpan[0]; 5794 } 5795 } 5796 5797 /** 5798 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this 5799 * TextView. 5800 * 5801 * @see #setHintTextColor(ColorStateList) 5802 * @see #getHintTextColors() 5803 * @see #setTextColor(int) 5804 * 5805 * @attr ref android.R.styleable#TextView_textColorHint 5806 */ 5807 @android.view.RemotableViewMethod 5808 public final void setHintTextColor(@ColorInt int color) { 5809 mHintTextColor = ColorStateList.valueOf(color); 5810 updateTextColors(); 5811 } 5812 5813 /** 5814 * Sets the color of the hint text. 5815 * 5816 * @see #getHintTextColors() 5817 * @see #setHintTextColor(int) 5818 * @see #setTextColor(ColorStateList) 5819 * @see #setLinkTextColor(ColorStateList) 5820 * 5821 * @attr ref android.R.styleable#TextView_textColorHint 5822 */ 5823 public final void setHintTextColor(ColorStateList colors) { 5824 mHintTextColor = colors; 5825 updateTextColors(); 5826 } 5827 5828 /** 5829 * @return the color of the hint text, for the different states of this TextView. 5830 * 5831 * @see #setHintTextColor(ColorStateList) 5832 * @see #setHintTextColor(int) 5833 * @see #setTextColor(ColorStateList) 5834 * @see #setLinkTextColor(ColorStateList) 5835 * 5836 * @attr ref android.R.styleable#TextView_textColorHint 5837 */ 5838 @InspectableProperty(name = "textColorHint") 5839 public final ColorStateList getHintTextColors() { 5840 return mHintTextColor; 5841 } 5842 5843 /** 5844 * <p>Return the current color selected to paint the hint text.</p> 5845 * 5846 * @return Returns the current hint text color. 5847 */ 5848 @ColorInt 5849 public final int getCurrentHintTextColor() { 5850 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 5851 } 5852 5853 /** 5854 * Sets the color of links in the text. 5855 * 5856 * @see #setLinkTextColor(ColorStateList) 5857 * @see #getLinkTextColors() 5858 * 5859 * @attr ref android.R.styleable#TextView_textColorLink 5860 */ 5861 @android.view.RemotableViewMethod 5862 public final void setLinkTextColor(@ColorInt int color) { 5863 mLinkTextColor = ColorStateList.valueOf(color); 5864 updateTextColors(); 5865 } 5866 5867 /** 5868 * Sets the color of links in the text. 5869 * 5870 * @see #setLinkTextColor(int) 5871 * @see #getLinkTextColors() 5872 * @see #setTextColor(ColorStateList) 5873 * @see #setHintTextColor(ColorStateList) 5874 * 5875 * @attr ref android.R.styleable#TextView_textColorLink 5876 */ 5877 public final void setLinkTextColor(ColorStateList colors) { 5878 mLinkTextColor = colors; 5879 updateTextColors(); 5880 } 5881 5882 /** 5883 * @return the list of colors used to paint the links in the text, for the different states of 5884 * this TextView 5885 * 5886 * @see #setLinkTextColor(ColorStateList) 5887 * @see #setLinkTextColor(int) 5888 * 5889 * @attr ref android.R.styleable#TextView_textColorLink 5890 */ 5891 @InspectableProperty(name = "textColorLink") 5892 public final ColorStateList getLinkTextColors() { 5893 return mLinkTextColor; 5894 } 5895 5896 /** 5897 * Sets the horizontal alignment of the text and the 5898 * vertical gravity that will be used when there is extra space 5899 * in the TextView beyond what is required for the text itself. 5900 * 5901 * @see android.view.Gravity 5902 * @attr ref android.R.styleable#TextView_gravity 5903 */ 5904 @android.view.RemotableViewMethod 5905 public void setGravity(int gravity) { 5906 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 5907 gravity |= Gravity.START; 5908 } 5909 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 5910 gravity |= Gravity.TOP; 5911 } 5912 5913 boolean newLayout = false; 5914 5915 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) 5916 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) { 5917 newLayout = true; 5918 } 5919 5920 if (gravity != mGravity) { 5921 invalidate(); 5922 } 5923 5924 mGravity = gravity; 5925 5926 if (mLayout != null && newLayout) { 5927 // XXX this is heavy-handed because no actual content changes. 5928 int want = mLayout.getWidth(); 5929 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 5930 5931 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 5932 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true); 5933 } 5934 } 5935 5936 /** 5937 * Returns the horizontal and vertical alignment of this TextView. 5938 * 5939 * @see android.view.Gravity 5940 * @attr ref android.R.styleable#TextView_gravity 5941 */ 5942 @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY) 5943 public int getGravity() { 5944 return mGravity; 5945 } 5946 5947 /** 5948 * Gets the flags on the Paint being used to display the text. 5949 * @return The flags on the Paint being used to display the text. 5950 * @see Paint#getFlags 5951 */ 5952 public int getPaintFlags() { 5953 return mTextPaint.getFlags(); 5954 } 5955 5956 /** 5957 * Sets flags on the Paint being used to display the text and 5958 * reflows the text if they are different from the old flags. 5959 * @see Paint#setFlags 5960 */ 5961 @android.view.RemotableViewMethod 5962 public void setPaintFlags(int flags) { 5963 if (mTextPaint.getFlags() != flags) { 5964 mTextPaint.setFlags(flags); 5965 5966 if (mLayout != null) { 5967 nullLayouts(); 5968 requestLayout(); 5969 invalidate(); 5970 } 5971 } 5972 } 5973 5974 /** 5975 * Sets whether the text should be allowed to be wider than the 5976 * View is. If false, it will be wrapped to the width of the View. 5977 * 5978 * @attr ref android.R.styleable#TextView_scrollHorizontally 5979 */ 5980 public void setHorizontallyScrolling(boolean whether) { 5981 if (mHorizontallyScrolling != whether) { 5982 mHorizontallyScrolling = whether; 5983 5984 if (mLayout != null) { 5985 nullLayouts(); 5986 requestLayout(); 5987 invalidate(); 5988 } 5989 } 5990 } 5991 5992 /** 5993 * Returns whether the text is allowed to be wider than the View. 5994 * If false, the text will be wrapped to the width of the View. 5995 * 5996 * @attr ref android.R.styleable#TextView_scrollHorizontally 5997 * @see #setHorizontallyScrolling(boolean) 5998 */ 5999 @InspectableProperty(name = "scrollHorizontally") 6000 public final boolean isHorizontallyScrollable() { 6001 return mHorizontallyScrolling; 6002 } 6003 6004 /** 6005 * Returns whether the text is allowed to be wider than the View. 6006 * If false, the text will be wrapped to the width of the View. 6007 * 6008 * @attr ref android.R.styleable#TextView_scrollHorizontally 6009 * @hide 6010 */ 6011 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 6012 public boolean getHorizontallyScrolling() { 6013 return mHorizontallyScrolling; 6014 } 6015 6016 /** 6017 * Sets the height of the TextView to be at least {@code minLines} tall. 6018 * <p> 6019 * This value is used for height calculation if LayoutParams does not force TextView to have an 6020 * exact height. Setting this value overrides other previous minimum height configurations such 6021 * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set 6022 * this value to 1. 6023 * 6024 * @param minLines the minimum height of TextView in terms of number of lines 6025 * 6026 * @see #getMinLines() 6027 * @see #setLines(int) 6028 * 6029 * @attr ref android.R.styleable#TextView_minLines 6030 */ 6031 @android.view.RemotableViewMethod 6032 public void setMinLines(int minLines) { 6033 mMinimum = minLines; 6034 mMinMode = LINES; 6035 6036 requestLayout(); 6037 invalidate(); 6038 } 6039 6040 /** 6041 * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum 6042 * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}. 6043 * 6044 * @return the minimum height of TextView in terms of number of lines or -1 if the minimum 6045 * height is not defined in lines 6046 * 6047 * @see #setMinLines(int) 6048 * @see #setLines(int) 6049 * 6050 * @attr ref android.R.styleable#TextView_minLines 6051 */ 6052 @InspectableProperty 6053 public int getMinLines() { 6054 return mMinMode == LINES ? mMinimum : -1; 6055 } 6056 6057 /** 6058 * Sets the height of the TextView to be at least {@code minPixels} tall. 6059 * <p> 6060 * This value is used for height calculation if LayoutParams does not force TextView to have an 6061 * exact height. Setting this value overrides previous minimum height configurations such as 6062 * {@link #setMinLines(int)} or {@link #setLines(int)}. 6063 * <p> 6064 * The value given here is different than {@link #setMinimumHeight(int)}. Between 6065 * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is 6066 * used to decide the final height. 6067 * 6068 * @param minPixels the minimum height of TextView in terms of pixels 6069 * 6070 * @see #getMinHeight() 6071 * @see #setHeight(int) 6072 * 6073 * @attr ref android.R.styleable#TextView_minHeight 6074 */ 6075 @android.view.RemotableViewMethod 6076 public void setMinHeight(int minPixels) { 6077 mMinimum = minPixels; 6078 mMinMode = PIXELS; 6079 6080 requestLayout(); 6081 invalidate(); 6082 } 6083 6084 /** 6085 * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was 6086 * set using {@link #setMinLines(int)} or {@link #setLines(int)}. 6087 * 6088 * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not 6089 * defined in pixels 6090 * 6091 * @see #setMinHeight(int) 6092 * @see #setHeight(int) 6093 * 6094 * @attr ref android.R.styleable#TextView_minHeight 6095 */ 6096 public int getMinHeight() { 6097 return mMinMode == PIXELS ? mMinimum : -1; 6098 } 6099 6100 /** 6101 * Sets the height of the TextView to be at most {@code maxLines} tall. 6102 * <p> 6103 * This value is used for height calculation if LayoutParams does not force TextView to have an 6104 * exact height. Setting this value overrides previous maximum height configurations such as 6105 * {@link #setMaxHeight(int)} or {@link #setLines(int)}. 6106 * 6107 * @param maxLines the maximum height of TextView in terms of number of lines 6108 * 6109 * @see #getMaxLines() 6110 * @see #setLines(int) 6111 * 6112 * @attr ref android.R.styleable#TextView_maxLines 6113 */ 6114 @android.view.RemotableViewMethod 6115 public void setMaxLines(int maxLines) { 6116 mMaximum = maxLines; 6117 mMaxMode = LINES; 6118 6119 requestLayout(); 6120 invalidate(); 6121 } 6122 6123 /** 6124 * Returns the maximum height of TextView in terms of number of lines or -1 if the 6125 * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}. 6126 * 6127 * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height 6128 * is not defined in lines. 6129 * 6130 * @see #setMaxLines(int) 6131 * @see #setLines(int) 6132 * 6133 * @attr ref android.R.styleable#TextView_maxLines 6134 */ 6135 @InspectableProperty 6136 public int getMaxLines() { 6137 return mMaxMode == LINES ? mMaximum : -1; 6138 } 6139 6140 /** 6141 * Sets the height of the TextView to be at most {@code maxPixels} tall. 6142 * <p> 6143 * This value is used for height calculation if LayoutParams does not force TextView to have an 6144 * exact height. Setting this value overrides previous maximum height configurations such as 6145 * {@link #setMaxLines(int)} or {@link #setLines(int)}. 6146 * 6147 * @param maxPixels the maximum height of TextView in terms of pixels 6148 * 6149 * @see #getMaxHeight() 6150 * @see #setHeight(int) 6151 * 6152 * @attr ref android.R.styleable#TextView_maxHeight 6153 */ 6154 @android.view.RemotableViewMethod 6155 public void setMaxHeight(int maxPixels) { 6156 mMaximum = maxPixels; 6157 mMaxMode = PIXELS; 6158 6159 requestLayout(); 6160 invalidate(); 6161 } 6162 6163 /** 6164 * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was 6165 * set using {@link #setMaxLines(int)} or {@link #setLines(int)}. 6166 * 6167 * @return the maximum height of TextView in terms of pixels or -1 if the maximum height 6168 * is not defined in pixels 6169 * 6170 * @see #setMaxHeight(int) 6171 * @see #setHeight(int) 6172 * 6173 * @attr ref android.R.styleable#TextView_maxHeight 6174 */ 6175 @InspectableProperty 6176 public int getMaxHeight() { 6177 return mMaxMode == PIXELS ? mMaximum : -1; 6178 } 6179 6180 /** 6181 * Sets the height of the TextView to be exactly {@code lines} tall. 6182 * <p> 6183 * This value is used for height calculation if LayoutParams does not force TextView to have an 6184 * exact height. Setting this value overrides previous minimum/maximum height configurations 6185 * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will 6186 * set this value to 1. 6187 * 6188 * @param lines the exact height of the TextView in terms of lines 6189 * 6190 * @see #setHeight(int) 6191 * 6192 * @attr ref android.R.styleable#TextView_lines 6193 */ 6194 @android.view.RemotableViewMethod 6195 public void setLines(int lines) { 6196 mMaximum = mMinimum = lines; 6197 mMaxMode = mMinMode = LINES; 6198 6199 requestLayout(); 6200 invalidate(); 6201 } 6202 6203 /** 6204 * Sets the height of the TextView to be exactly <code>pixels</code> tall. 6205 * <p> 6206 * This value is used for height calculation if LayoutParams does not force TextView to have an 6207 * exact height. Setting this value overrides previous minimum/maximum height configurations 6208 * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}. 6209 * 6210 * @param pixels the exact height of the TextView in terms of pixels 6211 * 6212 * @see #setLines(int) 6213 * 6214 * @attr ref android.R.styleable#TextView_height 6215 */ 6216 @android.view.RemotableViewMethod 6217 public void setHeight(int pixels) { 6218 mMaximum = mMinimum = pixels; 6219 mMaxMode = mMinMode = PIXELS; 6220 6221 requestLayout(); 6222 invalidate(); 6223 } 6224 6225 /** 6226 * Sets the width of the TextView to be at least {@code minEms} wide. 6227 * <p> 6228 * This value is used for width calculation if LayoutParams does not force TextView to have an 6229 * exact width. Setting this value overrides previous minimum width configurations such as 6230 * {@link #setMinWidth(int)} or {@link #setWidth(int)}. 6231 * 6232 * @param minEms the minimum width of TextView in terms of ems 6233 * 6234 * @see #getMinEms() 6235 * @see #setEms(int) 6236 * 6237 * @attr ref android.R.styleable#TextView_minEms 6238 */ 6239 @android.view.RemotableViewMethod 6240 public void setMinEms(int minEms) { 6241 mMinWidth = minEms; 6242 mMinWidthMode = EMS; 6243 6244 requestLayout(); 6245 invalidate(); 6246 } 6247 6248 /** 6249 * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set 6250 * using {@link #setMinWidth(int)} or {@link #setWidth(int)}. 6251 * 6252 * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not 6253 * defined in ems 6254 * 6255 * @see #setMinEms(int) 6256 * @see #setEms(int) 6257 * 6258 * @attr ref android.R.styleable#TextView_minEms 6259 */ 6260 @InspectableProperty 6261 public int getMinEms() { 6262 return mMinWidthMode == EMS ? mMinWidth : -1; 6263 } 6264 6265 /** 6266 * Sets the width of the TextView to be at least {@code minPixels} wide. 6267 * <p> 6268 * This value is used for width calculation if LayoutParams does not force TextView to have an 6269 * exact width. Setting this value overrides previous minimum width configurations such as 6270 * {@link #setMinEms(int)} or {@link #setEms(int)}. 6271 * <p> 6272 * The value given here is different than {@link #setMinimumWidth(int)}. Between 6273 * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used 6274 * to decide the final width. 6275 * 6276 * @param minPixels the minimum width of TextView in terms of pixels 6277 * 6278 * @see #getMinWidth() 6279 * @see #setWidth(int) 6280 * 6281 * @attr ref android.R.styleable#TextView_minWidth 6282 */ 6283 @android.view.RemotableViewMethod 6284 public void setMinWidth(int minPixels) { 6285 mMinWidth = minPixels; 6286 mMinWidthMode = PIXELS; 6287 6288 requestLayout(); 6289 invalidate(); 6290 } 6291 6292 /** 6293 * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set 6294 * using {@link #setMinEms(int)} or {@link #setEms(int)}. 6295 * 6296 * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not 6297 * defined in pixels 6298 * 6299 * @see #setMinWidth(int) 6300 * @see #setWidth(int) 6301 * 6302 * @attr ref android.R.styleable#TextView_minWidth 6303 */ 6304 @InspectableProperty 6305 public int getMinWidth() { 6306 return mMinWidthMode == PIXELS ? mMinWidth : -1; 6307 } 6308 6309 /** 6310 * Sets the width of the TextView to be at most {@code maxEms} wide. 6311 * <p> 6312 * This value is used for width calculation if LayoutParams does not force TextView to have an 6313 * exact width. Setting this value overrides previous maximum width configurations such as 6314 * {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 6315 * 6316 * @param maxEms the maximum width of TextView in terms of ems 6317 * 6318 * @see #getMaxEms() 6319 * @see #setEms(int) 6320 * 6321 * @attr ref android.R.styleable#TextView_maxEms 6322 */ 6323 @android.view.RemotableViewMethod 6324 public void setMaxEms(int maxEms) { 6325 mMaxWidth = maxEms; 6326 mMaxWidthMode = EMS; 6327 6328 requestLayout(); 6329 invalidate(); 6330 } 6331 6332 /** 6333 * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set 6334 * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 6335 * 6336 * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not 6337 * defined in ems 6338 * 6339 * @see #setMaxEms(int) 6340 * @see #setEms(int) 6341 * 6342 * @attr ref android.R.styleable#TextView_maxEms 6343 */ 6344 @InspectableProperty 6345 public int getMaxEms() { 6346 return mMaxWidthMode == EMS ? mMaxWidth : -1; 6347 } 6348 6349 /** 6350 * Sets the width of the TextView to be at most {@code maxPixels} wide. 6351 * <p> 6352 * This value is used for width calculation if LayoutParams does not force TextView to have an 6353 * exact width. Setting this value overrides previous maximum width configurations such as 6354 * {@link #setMaxEms(int)} or {@link #setEms(int)}. 6355 * 6356 * @param maxPixels the maximum width of TextView in terms of pixels 6357 * 6358 * @see #getMaxWidth() 6359 * @see #setWidth(int) 6360 * 6361 * @attr ref android.R.styleable#TextView_maxWidth 6362 */ 6363 @android.view.RemotableViewMethod 6364 public void setMaxWidth(int maxPixels) { 6365 mMaxWidth = maxPixels; 6366 mMaxWidthMode = PIXELS; 6367 6368 requestLayout(); 6369 invalidate(); 6370 } 6371 6372 /** 6373 * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set 6374 * using {@link #setMaxEms(int)} or {@link #setEms(int)}. 6375 * 6376 * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not 6377 * defined in pixels 6378 * 6379 * @see #setMaxWidth(int) 6380 * @see #setWidth(int) 6381 * 6382 * @attr ref android.R.styleable#TextView_maxWidth 6383 */ 6384 @InspectableProperty 6385 public int getMaxWidth() { 6386 return mMaxWidthMode == PIXELS ? mMaxWidth : -1; 6387 } 6388 6389 /** 6390 * Sets the width of the TextView to be exactly {@code ems} wide. 6391 * 6392 * This value is used for width calculation if LayoutParams does not force TextView to have an 6393 * exact width. Setting this value overrides previous minimum/maximum configurations such as 6394 * {@link #setMinEms(int)} or {@link #setMaxEms(int)}. 6395 * 6396 * @param ems the exact width of the TextView in terms of ems 6397 * 6398 * @see #setWidth(int) 6399 * 6400 * @attr ref android.R.styleable#TextView_ems 6401 */ 6402 @android.view.RemotableViewMethod 6403 public void setEms(int ems) { 6404 mMaxWidth = mMinWidth = ems; 6405 mMaxWidthMode = mMinWidthMode = EMS; 6406 6407 requestLayout(); 6408 invalidate(); 6409 } 6410 6411 /** 6412 * Sets the width of the TextView to be exactly {@code pixels} wide. 6413 * <p> 6414 * This value is used for width calculation if LayoutParams does not force TextView to have an 6415 * exact width. Setting this value overrides previous minimum/maximum width configurations 6416 * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}. 6417 * 6418 * @param pixels the exact width of the TextView in terms of pixels 6419 * 6420 * @see #setEms(int) 6421 * 6422 * @attr ref android.R.styleable#TextView_width 6423 */ 6424 @android.view.RemotableViewMethod 6425 public void setWidth(int pixels) { 6426 mMaxWidth = mMinWidth = pixels; 6427 mMaxWidthMode = mMinWidthMode = PIXELS; 6428 6429 requestLayout(); 6430 invalidate(); 6431 } 6432 6433 /** 6434 * Sets line spacing for this TextView. Each line other than the last line will have its height 6435 * multiplied by {@code mult} and have {@code add} added to it. 6436 * 6437 * @param add The value in pixels that should be added to each line other than the last line. 6438 * This will be applied after the multiplier 6439 * @param mult The value by which each line height other than the last line will be multiplied 6440 * by 6441 * 6442 * @attr ref android.R.styleable#TextView_lineSpacingExtra 6443 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 6444 */ 6445 public void setLineSpacing(float add, float mult) { 6446 if (mSpacingAdd != add || mSpacingMult != mult) { 6447 mSpacingAdd = add; 6448 mSpacingMult = mult; 6449 6450 if (mLayout != null) { 6451 nullLayouts(); 6452 requestLayout(); 6453 invalidate(); 6454 } 6455 } 6456 } 6457 6458 /** 6459 * Gets the line spacing multiplier 6460 * 6461 * @return the value by which each line's height is multiplied to get its actual height. 6462 * 6463 * @see #setLineSpacing(float, float) 6464 * @see #getLineSpacingExtra() 6465 * 6466 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 6467 */ 6468 @InspectableProperty 6469 public float getLineSpacingMultiplier() { 6470 return mSpacingMult; 6471 } 6472 6473 /** 6474 * Gets the line spacing extra space 6475 * 6476 * @return the extra space that is added to the height of each lines of this TextView. 6477 * 6478 * @see #setLineSpacing(float, float) 6479 * @see #getLineSpacingMultiplier() 6480 * 6481 * @attr ref android.R.styleable#TextView_lineSpacingExtra 6482 */ 6483 @InspectableProperty 6484 public float getLineSpacingExtra() { 6485 return mSpacingAdd; 6486 } 6487 6488 /** 6489 * Sets an explicit line height for this TextView. This is equivalent to the vertical distance 6490 * between subsequent baselines in the TextView. 6491 * 6492 * @param lineHeight the line height in pixels 6493 * 6494 * @see #setLineSpacing(float, float) 6495 * @see #getLineSpacingExtra() 6496 * 6497 * @attr ref android.R.styleable#TextView_lineHeight 6498 */ 6499 @android.view.RemotableViewMethod 6500 public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { 6501 setLineHeightPx(lineHeight); 6502 } 6503 6504 private void setLineHeightPx(@Px @FloatRange(from = 0) float lineHeight) { 6505 Preconditions.checkArgumentNonNegative(lineHeight, 6506 "Expecting non-negative lineHeight while the input is " + lineHeight); 6507 6508 final int fontHeight = getPaint().getFontMetricsInt(null); 6509 // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw. 6510 // TODO(b/274974975): should this also check if lineSpacing needs to change? 6511 if (lineHeight != fontHeight) { 6512 // Set lineSpacingExtra by the difference of lineSpacing with lineHeight 6513 setLineSpacing(lineHeight - fontHeight, 1f); 6514 6515 mLineHeightComplexDimen = 6516 TypedValue.createComplexDimension(lineHeight, TypedValue.COMPLEX_UNIT_PX); 6517 } 6518 } 6519 6520 /** 6521 * Sets an explicit line height to a given unit and value for this TextView. This is equivalent 6522 * to the vertical distance between subsequent baselines in the TextView. See {@link 6523 * TypedValue} for the possible dimension units. 6524 * 6525 * @param unit The desired dimension unit. SP units are strongly recommended so that line height 6526 * stays proportional to the text size when fonts are scaled up for accessibility. 6527 * @param lineHeight The desired line height in the given units. 6528 * 6529 * @see #setLineSpacing(float, float) 6530 * @see #getLineSpacingExtra() 6531 * 6532 * @attr ref android.R.styleable#TextView_lineHeight 6533 */ 6534 @android.view.RemotableViewMethod 6535 public void setLineHeight( 6536 @TypedValue.ComplexDimensionUnit int unit, 6537 @FloatRange(from = 0) float lineHeight 6538 ) { 6539 var metrics = getDisplayMetricsOrSystem(); 6540 // We can avoid the recalculation if we know non-linear font scaling isn't being used 6541 // (an optimization for the majority case). 6542 // We also don't try to do the recalculation unless both textSize and lineHeight are in SP. 6543 if (!FontScaleConverterFactory.isNonLinearFontScalingActive( 6544 getResources().getConfiguration().fontScale) 6545 || unit != TypedValue.COMPLEX_UNIT_SP 6546 || mTextSizeUnit != TypedValue.COMPLEX_UNIT_SP 6547 ) { 6548 setLineHeightPx(TypedValue.applyDimension(unit, lineHeight, metrics)); 6549 6550 // Do this last so it overwrites what setLineHeightPx() sets it to. 6551 mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit); 6552 return; 6553 } 6554 6555 // Recalculate a proportional line height when non-linear font scaling is in effect. 6556 // Otherwise, a desired 2x line height at font scale 1.0 will not be 2x at font scale 2.0, 6557 // due to non-linear font scaling compressing higher SP sizes. See b/273326061 for details. 6558 // We know they are using SP units for both the text size and the line height 6559 // at this point, so determine the ratio between them. This is the *intended* line spacing 6560 // multiplier if font scale == 1.0. We can then determine what the pixel value for the line 6561 // height would be if we preserved proportions. 6562 var textSizePx = getTextSize(); 6563 var textSizeSp = TypedValue.convertPixelsToDimension( 6564 TypedValue.COMPLEX_UNIT_SP, 6565 textSizePx, 6566 metrics 6567 ); 6568 var ratio = lineHeight / textSizeSp; 6569 setLineHeightPx(textSizePx * ratio); 6570 6571 // Do this last so it overwrites what setLineHeightPx() sets it to. 6572 mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit); 6573 } 6574 6575 private void maybeRecalculateLineHeight() { 6576 if (mLineHeightComplexDimen == 0) { 6577 return; 6578 } 6579 int unit = TypedValue.getUnitFromComplexDimension(mLineHeightComplexDimen); 6580 if (unit != TypedValue.COMPLEX_UNIT_SP) { 6581 // The lineHeight was never supplied in SP, so we didn't do any fancy recalculations 6582 // in setLineHeight(). We don't need to recalculate. 6583 return; 6584 } 6585 6586 setLineHeight(unit, TypedValue.complexToFloat(mLineHeightComplexDimen)); 6587 } 6588 6589 /** 6590 * Set Highlights 6591 * 6592 * @param highlights A highlight object. Call with null for reset. 6593 * 6594 * @see #getHighlights() 6595 * @see Highlights 6596 */ 6597 public void setHighlights(@Nullable Highlights highlights) { 6598 mHighlights = highlights; 6599 mHighlightPathsBogus = true; 6600 invalidate(); 6601 } 6602 6603 /** 6604 * Returns highlights 6605 * 6606 * @return a highlight to be drawn. null if no highlight was set. 6607 * 6608 * @see #setHighlights(Highlights) 6609 * @see Highlights 6610 * 6611 */ 6612 @Nullable 6613 public Highlights getHighlights() { 6614 return mHighlights; 6615 } 6616 6617 /** 6618 * Sets the search result ranges with flatten range representation. 6619 * 6620 * Ranges are represented of flattened inclusive start and exclusive end integers array. The 6621 * inclusive start offset of the {@code i}-th range is stored in {@code 2 * i}-th of the array. 6622 * The exclusive end offset of the {@code i}-th range is stored in {@code 2* i + 1}-th of the 6623 * array. For example, the two ranges: (1, 2) and (3, 4) are flattened into single int array 6624 * [1, 2, 3, 4]. 6625 * 6626 * TextView will render the search result with the highlights with specified color in the theme. 6627 * If there is a focused search result, it is rendered with focused color. By calling this 6628 * method, the focused search index will be cleared. 6629 * 6630 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6631 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6632 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6633 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6634 * 6635 * @see #getSearchResultHighlights() 6636 * @see #setFocusedSearchResultIndex(int) 6637 * @see #getFocusedSearchResultIndex() 6638 * @see #setSearchResultHighlightColor(int) 6639 * @see #getSearchResultHighlightColor() 6640 * @see #setFocusedSearchResultHighlightColor(int) 6641 * @see #getFocusedSearchResultHighlightColor() 6642 * 6643 * @param ranges the flatten ranges of the search result. null for clear. 6644 */ 6645 public void setSearchResultHighlights(@Nullable int... ranges) { 6646 if (ranges == null) { 6647 mSearchResultHighlights = null; 6648 mHighlightPathsBogus = true; 6649 return; 6650 } 6651 if (ranges.length % 2 == 1) { 6652 throw new IllegalArgumentException( 6653 "Flatten ranges must have even numbered elements"); 6654 } 6655 for (int j = 0; j < ranges.length / 2; ++j) { 6656 int start = ranges[j * 2]; 6657 int end = ranges[j * 2 + 1]; 6658 if (start > end) { 6659 throw new IllegalArgumentException( 6660 "Reverse range found in the flatten range: " + start + ", " + end + "" 6661 + " at " + j + "-th range"); 6662 } 6663 } 6664 mHighlightPathsBogus = true; 6665 mSearchResultHighlights = ranges; 6666 mFocusedSearchResultIndex = FOCUSED_SEARCH_RESULT_INDEX_NONE; 6667 invalidate(); 6668 } 6669 6670 /** 6671 * Gets the current search result ranges. 6672 * 6673 * @see #setSearchResultHighlights(int[]) 6674 * @see #setFocusedSearchResultIndex(int) 6675 * @see #getFocusedSearchResultIndex() 6676 * @see #setSearchResultHighlightColor(int) 6677 * @see #getSearchResultHighlightColor() 6678 * @see #setFocusedSearchResultHighlightColor(int) 6679 * @see #getFocusedSearchResultHighlightColor() 6680 * 6681 * @return a flatten search result ranges. null if not available. 6682 */ 6683 @Nullable 6684 public int[] getSearchResultHighlights() { 6685 return mSearchResultHighlights; 6686 } 6687 6688 /** 6689 * A special index used for {@link #setFocusedSearchResultIndex(int)} and 6690 * {@link #getFocusedSearchResultIndex()} inidicating there is no focused search result. 6691 */ 6692 public static final int FOCUSED_SEARCH_RESULT_INDEX_NONE = -1; 6693 6694 /** 6695 * Sets the focused search result index. 6696 * 6697 * The focused search result is drawn in a focused color. 6698 * Calling {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} for clearing focused search result. 6699 * 6700 * This method must be called after setting search result ranges by 6701 * {@link #setSearchResultHighlights(int[])}. 6702 * 6703 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6704 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6705 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6706 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6707 * 6708 * @see #setSearchResultHighlights(int[]) 6709 * @see #getSearchResultHighlights() 6710 * @see #setFocusedSearchResultIndex(int) 6711 * @see #getFocusedSearchResultIndex() 6712 * @see #setSearchResultHighlightColor(int) 6713 * @see #getSearchResultHighlightColor() 6714 * @see #setFocusedSearchResultHighlightColor(int) 6715 * @see #getFocusedSearchResultHighlightColor() 6716 * 6717 * @param index a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} 6718 */ 6719 public void setFocusedSearchResultIndex(int index) { 6720 if (mSearchResultHighlights == null) { 6721 throw new IllegalArgumentException("Search result range must be set beforehand."); 6722 } 6723 if (index < -1 || index >= mSearchResultHighlights.length / 2) { 6724 throw new IllegalArgumentException("Focused index(" + index + ") must be larger than " 6725 + "-1 and less than range count(" + (mSearchResultHighlights.length / 2) + ")"); 6726 } 6727 mFocusedSearchResultIndex = index; 6728 mHighlightPathsBogus = true; 6729 invalidate(); 6730 } 6731 6732 /** 6733 * Gets the focused search result index. 6734 * 6735 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6736 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6737 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6738 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6739 * 6740 * @see #setSearchResultHighlights(int[]) 6741 * @see #getSearchResultHighlights() 6742 * @see #setFocusedSearchResultIndex(int) 6743 * @see #getFocusedSearchResultIndex() 6744 * @see #setSearchResultHighlightColor(int) 6745 * @see #getSearchResultHighlightColor() 6746 * @see #setFocusedSearchResultHighlightColor(int) 6747 * @see #getFocusedSearchResultHighlightColor() 6748 6749 * @return a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} 6750 */ 6751 public int getFocusedSearchResultIndex() { 6752 return mFocusedSearchResultIndex; 6753 } 6754 6755 /** 6756 * Sets the search result highlight color. 6757 * 6758 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6759 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6760 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6761 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6762 * 6763 * @see #setSearchResultHighlights(int[]) 6764 * @see #getSearchResultHighlights() 6765 * @see #setFocusedSearchResultIndex(int) 6766 * @see #getFocusedSearchResultIndex() 6767 * @see #setSearchResultHighlightColor(int) 6768 * @see #getSearchResultHighlightColor() 6769 * @see #setFocusedSearchResultHighlightColor(int) 6770 * @see #getFocusedSearchResultHighlightColor() 6771 6772 * @param color a search result highlight color. 6773 */ 6774 public void setSearchResultHighlightColor(@ColorInt int color) { 6775 mSearchResultHighlightColor = color; 6776 } 6777 6778 /** 6779 * Gets the search result highlight color. 6780 * 6781 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6782 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6783 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6784 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6785 * 6786 * @see #setSearchResultHighlights(int[]) 6787 * @see #getSearchResultHighlights() 6788 * @see #setFocusedSearchResultIndex(int) 6789 * @see #getFocusedSearchResultIndex() 6790 * @see #setSearchResultHighlightColor(int) 6791 * @see #getSearchResultHighlightColor() 6792 * @see #setFocusedSearchResultHighlightColor(int) 6793 * @see #getFocusedSearchResultHighlightColor() 6794 6795 * @return a search result highlight color. 6796 */ 6797 @ColorInt 6798 public int getSearchResultHighlightColor() { 6799 return mSearchResultHighlightColor; 6800 } 6801 6802 /** 6803 * Sets focused search result highlight color. 6804 * 6805 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6806 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6807 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6808 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6809 * 6810 * @see #setSearchResultHighlights(int[]) 6811 * @see #getSearchResultHighlights() 6812 * @see #setFocusedSearchResultIndex(int) 6813 * @see #getFocusedSearchResultIndex() 6814 * @see #setSearchResultHighlightColor(int) 6815 * @see #getSearchResultHighlightColor() 6816 * @see #setFocusedSearchResultHighlightColor(int) 6817 * @see #getFocusedSearchResultHighlightColor() 6818 6819 * @param color a focused search result highlight color. 6820 */ 6821 public void setFocusedSearchResultHighlightColor(@ColorInt int color) { 6822 mFocusedSearchResultHighlightColor = color; 6823 } 6824 6825 /** 6826 * Gets focused search result highlight color. 6827 * 6828 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6829 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6830 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6831 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6832 * 6833 * @see #setSearchResultHighlights(int[]) 6834 * @see #getSearchResultHighlights() 6835 * @see #setFocusedSearchResultIndex(int) 6836 * @see #getFocusedSearchResultIndex() 6837 * @see #setSearchResultHighlightColor(int) 6838 * @see #getSearchResultHighlightColor() 6839 * @see #setFocusedSearchResultHighlightColor(int) 6840 * @see #getFocusedSearchResultHighlightColor() 6841 6842 * @return a focused search result highlight color. 6843 */ 6844 @ColorInt 6845 public int getFocusedSearchResultHighlightColor() { 6846 return mFocusedSearchResultHighlightColor; 6847 } 6848 6849 /** 6850 * Highlights the text range (from inclusive start offset to exclusive end offset) to show what 6851 * will be selected by the ongoing select handwriting gesture. While the gesture preview 6852 * highlight is shown, the selection or cursor is hidden. If the text or selection is changed, 6853 * the gesture preview highlight will be cleared. 6854 */ 6855 private void setSelectGesturePreviewHighlight(int start, int end) { 6856 // Selection preview highlight color is the same as selection highlight color. 6857 setGesturePreviewHighlight(start, end, mHighlightColor); 6858 } 6859 6860 /** 6861 * Highlights the text range (from inclusive start offset to exclusive end offset) to show what 6862 * will be deleted by the ongoing delete handwriting gesture. While the gesture preview 6863 * highlight is shown, the selection or cursor is hidden. If the text or selection is changed, 6864 * the gesture preview highlight will be cleared. 6865 */ 6866 private void setDeleteGesturePreviewHighlight(int start, int end) { 6867 // Deletion preview highlight color is 20% opacity of the default text color. 6868 int color = mTextColor.getDefaultColor(); 6869 color = ColorUtils.setAlphaComponent(color, (int) (0.2f * Color.alpha(color))); 6870 setGesturePreviewHighlight(start, end, color); 6871 } 6872 6873 private void setGesturePreviewHighlight(int start, int end, int color) { 6874 mGesturePreviewHighlightStart = start; 6875 mGesturePreviewHighlightEnd = end; 6876 if (mGesturePreviewHighlightPaint == null) { 6877 mGesturePreviewHighlightPaint = new Paint(); 6878 mGesturePreviewHighlightPaint.setStyle(Paint.Style.FILL); 6879 } 6880 mGesturePreviewHighlightPaint.setColor(color); 6881 6882 if (mEditor != null) { 6883 mEditor.hideCursorAndSpanControllers(); 6884 mEditor.stopTextActionModeWithPreservingSelection(); 6885 } 6886 6887 mHighlightPathsBogus = true; 6888 invalidate(); 6889 } 6890 6891 private void clearGesturePreviewHighlight() { 6892 mGesturePreviewHighlightStart = -1; 6893 mGesturePreviewHighlightEnd = -1; 6894 mHighlightPathsBogus = true; 6895 invalidate(); 6896 } 6897 6898 boolean hasGesturePreviewHighlight() { 6899 return mGesturePreviewHighlightStart >= 0; 6900 } 6901 6902 /** 6903 * Convenience method to append the specified text to the TextView's 6904 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 6905 * if it was not already editable. 6906 * 6907 * @param text text to be appended to the already displayed text 6908 */ 6909 public final void append(CharSequence text) { 6910 append(text, 0, text.length()); 6911 } 6912 6913 /** 6914 * Convenience method to append the specified text slice to the TextView's 6915 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 6916 * if it was not already editable. 6917 * 6918 * @param text text to be appended to the already displayed text 6919 * @param start the index of the first character in the {@code text} 6920 * @param end the index of the character following the last character in the {@code text} 6921 * 6922 * @see Appendable#append(CharSequence, int, int) 6923 */ 6924 public void append(CharSequence text, int start, int end) { 6925 if (!(mText instanceof Editable)) { 6926 setText(mText, BufferType.EDITABLE); 6927 } 6928 6929 ((Editable) mText).append(text, start, end); 6930 6931 if (mAutoLinkMask != 0) { 6932 boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask); 6933 // Do not change the movement method for text that support text selection as it 6934 // would prevent an arbitrary cursor displacement. 6935 if (linksWereAdded && mLinksClickable && !textCanBeSelected()) { 6936 setMovementMethod(LinkMovementMethod.getInstance()); 6937 } 6938 } 6939 } 6940 6941 private void updateTextColors() { 6942 boolean inval = false; 6943 final int[] drawableState = getDrawableState(); 6944 int color = mTextColor.getColorForState(drawableState, 0); 6945 if (color != mCurTextColor) { 6946 mCurTextColor = color; 6947 inval = true; 6948 } 6949 if (mLinkTextColor != null) { 6950 color = mLinkTextColor.getColorForState(drawableState, 0); 6951 if (color != mTextPaint.linkColor) { 6952 mTextPaint.linkColor = color; 6953 inval = true; 6954 } 6955 } 6956 if (mHintTextColor != null) { 6957 color = mHintTextColor.getColorForState(drawableState, 0); 6958 if (color != mCurHintTextColor) { 6959 mCurHintTextColor = color; 6960 if (mText.length() == 0) { 6961 inval = true; 6962 } 6963 } 6964 } 6965 if (inval) { 6966 // Text needs to be redrawn with the new color 6967 if (mEditor != null) mEditor.invalidateTextDisplayList(); 6968 invalidate(); 6969 } 6970 } 6971 6972 @Override 6973 protected void drawableStateChanged() { 6974 super.drawableStateChanged(); 6975 6976 if (mTextColor != null && mTextColor.isStateful() 6977 || (mHintTextColor != null && mHintTextColor.isStateful()) 6978 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 6979 updateTextColors(); 6980 } 6981 6982 if (mDrawables != null) { 6983 final int[] state = getDrawableState(); 6984 for (Drawable dr : mDrawables.mShowing) { 6985 if (dr != null && dr.isStateful() && dr.setState(state)) { 6986 invalidateDrawable(dr); 6987 } 6988 } 6989 } 6990 } 6991 6992 @Override 6993 public void drawableHotspotChanged(float x, float y) { 6994 super.drawableHotspotChanged(x, y); 6995 6996 if (mDrawables != null) { 6997 for (Drawable dr : mDrawables.mShowing) { 6998 if (dr != null) { 6999 dr.setHotspot(x, y); 7000 } 7001 } 7002 } 7003 } 7004 7005 @Override 7006 public Parcelable onSaveInstanceState() { 7007 Parcelable superState = super.onSaveInstanceState(); 7008 7009 // Save state if we are forced to 7010 final boolean freezesText = getFreezesText(); 7011 boolean hasSelection = false; 7012 int start = -1; 7013 int end = -1; 7014 7015 if (mText != null) { 7016 start = getSelectionStart(); 7017 end = getSelectionEnd(); 7018 if (start >= 0 || end >= 0) { 7019 // Or save state if there is a selection 7020 hasSelection = true; 7021 } 7022 } 7023 7024 if (freezesText || hasSelection) { 7025 SavedState ss = new SavedState(superState); 7026 7027 if (freezesText) { 7028 if (mText instanceof Spanned) { 7029 final Spannable sp = new SpannableStringBuilder(mText); 7030 7031 if (mEditor != null) { 7032 removeMisspelledSpans(sp); 7033 sp.removeSpan(mEditor.mSuggestionRangeSpan); 7034 } 7035 7036 ss.text = sp; 7037 } else { 7038 ss.text = mText.toString(); 7039 } 7040 } 7041 7042 if (hasSelection) { 7043 // XXX Should also save the current scroll position! 7044 ss.selStart = start; 7045 ss.selEnd = end; 7046 } 7047 7048 if (isFocused() && start >= 0 && end >= 0) { 7049 ss.frozenWithFocus = true; 7050 } 7051 7052 ss.error = getError(); 7053 7054 if (mEditor != null) { 7055 ss.editorState = mEditor.saveInstanceState(); 7056 } 7057 return ss; 7058 } 7059 7060 return superState; 7061 } 7062 7063 void removeMisspelledSpans(Spannable spannable) { 7064 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), 7065 SuggestionSpan.class); 7066 for (int i = 0; i < suggestionSpans.length; i++) { 7067 int flags = suggestionSpans[i].getFlags(); 7068 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 7069 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { 7070 spannable.removeSpan(suggestionSpans[i]); 7071 } 7072 } 7073 } 7074 7075 @Override 7076 public void onRestoreInstanceState(Parcelable state) { 7077 if (!(state instanceof SavedState)) { 7078 super.onRestoreInstanceState(state); 7079 return; 7080 } 7081 7082 SavedState ss = (SavedState) state; 7083 super.onRestoreInstanceState(ss.getSuperState()); 7084 7085 // XXX restore buffer type too, as well as lots of other stuff 7086 if (ss.text != null) { 7087 setText(ss.text); 7088 } 7089 7090 if (ss.selStart >= 0 && ss.selEnd >= 0) { 7091 if (mSpannable != null) { 7092 int len = mText.length(); 7093 7094 if (ss.selStart > len || ss.selEnd > len) { 7095 String restored = ""; 7096 7097 if (ss.text != null) { 7098 restored = "(restored) "; 7099 } 7100 7101 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd 7102 + " out of range for " + restored + "text " + mText); 7103 } else { 7104 Selection.setSelection(mSpannable, ss.selStart, ss.selEnd); 7105 7106 if (ss.frozenWithFocus) { 7107 createEditorIfNeeded(); 7108 mEditor.mFrozenWithFocus = true; 7109 } 7110 } 7111 } 7112 } 7113 7114 if (ss.error != null) { 7115 final CharSequence error = ss.error; 7116 // Display the error later, after the first layout pass 7117 post(new Runnable() { 7118 public void run() { 7119 if (mEditor == null || !mEditor.mErrorWasChanged) { 7120 setError(error); 7121 } 7122 } 7123 }); 7124 } 7125 7126 if (ss.editorState != null) { 7127 createEditorIfNeeded(); 7128 mEditor.restoreInstanceState(ss.editorState); 7129 } 7130 } 7131 7132 /** 7133 * Control whether this text view saves its entire text contents when 7134 * freezing to an icicle, in addition to dynamic state such as cursor 7135 * position. By default this is false, not saving the text. Set to true 7136 * if the text in the text view is not being saved somewhere else in 7137 * persistent storage (such as in a content provider) so that if the 7138 * view is later thawed the user will not lose their data. For 7139 * {@link android.widget.EditText} it is always enabled, regardless of 7140 * the value of the attribute. 7141 * 7142 * @param freezesText Controls whether a frozen icicle should include the 7143 * entire text data: true to include it, false to not. 7144 * 7145 * @attr ref android.R.styleable#TextView_freezesText 7146 */ 7147 @android.view.RemotableViewMethod 7148 public void setFreezesText(boolean freezesText) { 7149 mFreezesText = freezesText; 7150 } 7151 7152 /** 7153 * Return whether this text view is including its entire text contents 7154 * in frozen icicles. For {@link android.widget.EditText} it always returns true. 7155 * 7156 * @return Returns true if text is included, false if it isn't. 7157 * 7158 * @see #setFreezesText 7159 */ 7160 @InspectableProperty 7161 public boolean getFreezesText() { 7162 return mFreezesText; 7163 } 7164 7165 /////////////////////////////////////////////////////////////////////////// 7166 7167 /** 7168 * Sets the Factory used to create new {@link Editable Editables}. 7169 * 7170 * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used 7171 * 7172 * @see android.text.Editable.Factory 7173 * @see android.widget.TextView.BufferType#EDITABLE 7174 */ 7175 public final void setEditableFactory(Editable.Factory factory) { 7176 mEditableFactory = factory; 7177 setText(mText); 7178 } 7179 7180 /** 7181 * Sets the Factory used to create new {@link Spannable Spannables}. 7182 * 7183 * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used 7184 * 7185 * @see android.text.Spannable.Factory 7186 * @see android.widget.TextView.BufferType#SPANNABLE 7187 */ 7188 public final void setSpannableFactory(Spannable.Factory factory) { 7189 mSpannableFactory = factory; 7190 setText(mText); 7191 } 7192 7193 /** 7194 * Sets the text to be displayed. TextView <em>does not</em> accept 7195 * HTML-like formatting, which you can do with text strings in XML resource files. 7196 * To style your strings, attach android.text.style.* objects to a 7197 * {@link android.text.SpannableString}, or see the 7198 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 7199 * Available Resource Types</a> documentation for an example of setting 7200 * formatted text in the XML resource file. 7201 * <p/> 7202 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 7203 * intermediate {@link Spannable Spannables}. Likewise it will use 7204 * {@link android.text.Editable.Factory} to create final or intermediate 7205 * {@link Editable Editables}. 7206 * 7207 * If the passed text is a {@link PrecomputedText} but the parameters used to create the 7208 * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure 7209 * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this. 7210 * 7211 * @param text text to be displayed 7212 * 7213 * @attr ref android.R.styleable#TextView_text 7214 * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the 7215 * parameters used to create the PrecomputedText mismatches 7216 * with this TextView. 7217 */ 7218 @android.view.RemotableViewMethod(asyncImpl = "setTextAsync") 7219 public final void setText(CharSequence text) { 7220 setText(text, mBufferType); 7221 } 7222 7223 /** 7224 * RemotableViewMethod's asyncImpl of {@link #setText(CharSequence)}. 7225 * This should be called on a background thread, and returns a Runnable which is then must be 7226 * called on the main thread to complete the operation and set text. 7227 * @param text text to be displayed 7228 * @return Runnable that sets text; must be called on the main thread by the caller of this 7229 * method to complete the operation 7230 * @hide 7231 */ 7232 @NonNull 7233 public Runnable setTextAsync(@Nullable CharSequence text) { 7234 return () -> setText(text); 7235 } 7236 7237 /** 7238 * Sets the text to be displayed but retains the cursor position. Same as 7239 * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the 7240 * new text. 7241 * <p/> 7242 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 7243 * intermediate {@link Spannable Spannables}. Likewise it will use 7244 * {@link android.text.Editable.Factory} to create final or intermediate 7245 * {@link Editable Editables}. 7246 * 7247 * @param text text to be displayed 7248 * 7249 * @see #setText(CharSequence) 7250 */ 7251 @android.view.RemotableViewMethod 7252 public final void setTextKeepState(CharSequence text) { 7253 setTextKeepState(text, mBufferType); 7254 } 7255 7256 /** 7257 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}. 7258 * <p/> 7259 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 7260 * intermediate {@link Spannable Spannables}. Likewise it will use 7261 * {@link android.text.Editable.Factory} to create final or intermediate 7262 * {@link Editable Editables}. 7263 * 7264 * Subclasses overriding this method should ensure that the following post condition holds, 7265 * in order to guarantee the safety of the view's measurement and layout operations: 7266 * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed} 7267 * will be different from {@code null}. 7268 * 7269 * @param text text to be displayed 7270 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 7271 * stored as a static text, styleable/spannable text, or editable text 7272 * 7273 * @see #setText(CharSequence) 7274 * @see android.widget.TextView.BufferType 7275 * @see #setSpannableFactory(Spannable.Factory) 7276 * @see #setEditableFactory(Editable.Factory) 7277 * 7278 * @attr ref android.R.styleable#TextView_text 7279 * @attr ref android.R.styleable#TextView_bufferType 7280 */ 7281 public void setText(CharSequence text, BufferType type) { 7282 setText(text, type, true, 0); 7283 7284 // drop any potential mCharWrappper leaks 7285 mCharWrapper = null; 7286 } 7287 7288 @UnsupportedAppUsage 7289 private void setText(CharSequence text, BufferType type, 7290 boolean notifyBefore, int oldlen) { 7291 if (mEditor != null) { 7292 mEditor.beforeSetText(); 7293 } 7294 mTextSetFromXmlOrResourceId = false; 7295 if (text == null) { 7296 text = ""; 7297 } 7298 7299 // If suggestions are not enabled, remove the suggestion spans from the text 7300 if (!isSuggestionsEnabled()) { 7301 text = removeSuggestionSpans(text); 7302 } 7303 7304 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 7305 7306 if (text instanceof Spanned 7307 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 7308 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { 7309 setHorizontalFadingEdgeEnabled(true); 7310 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 7311 } else { 7312 setHorizontalFadingEdgeEnabled(false); 7313 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 7314 } 7315 setEllipsize(TextUtils.TruncateAt.MARQUEE); 7316 } 7317 7318 int n = mFilters.length; 7319 for (int i = 0; i < n; i++) { 7320 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); 7321 if (out != null) { 7322 text = out; 7323 } 7324 } 7325 7326 if (notifyBefore) { 7327 if (mText != null) { 7328 oldlen = mText.length(); 7329 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 7330 } else { 7331 sendBeforeTextChanged("", 0, 0, text.length()); 7332 } 7333 } 7334 7335 boolean needEditableForNotification = false; 7336 7337 if (mListeners != null && mListeners.size() != 0) { 7338 needEditableForNotification = true; 7339 } 7340 7341 PrecomputedText precomputed = 7342 (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 7343 if (type == BufferType.EDITABLE || getKeyListener() != null 7344 || needEditableForNotification) { 7345 createEditorIfNeeded(); 7346 mEditor.forgetUndoRedo(); 7347 mEditor.scheduleRestartInputForSetText(); 7348 Editable t = mEditableFactory.newEditable(text); 7349 text = t; 7350 setFilters(t, mFilters); 7351 } else if (precomputed != null) { 7352 if (mTextDir == null) { 7353 mTextDir = getTextDirectionHeuristic(); 7354 } 7355 final @PrecomputedText.Params.CheckResultUsableResult int checkResult = 7356 precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, 7357 mHyphenationFrequency, LineBreakConfig.getLineBreakConfig( 7358 mLineBreakStyle, mLineBreakWordStyle)); 7359 switch (checkResult) { 7360 case PrecomputedText.Params.UNUSABLE: 7361 throw new IllegalArgumentException( 7362 "PrecomputedText's Parameters don't match the parameters of this TextView." 7363 + "Consider using setTextMetricsParams(precomputedText.getParams()) " 7364 + "to override the settings of this TextView: " 7365 + "PrecomputedText: " + precomputed.getParams() 7366 + "TextView: " + getTextMetricsParams()); 7367 case PrecomputedText.Params.NEED_RECOMPUTE: 7368 precomputed = PrecomputedText.create(precomputed, getTextMetricsParams()); 7369 break; 7370 case PrecomputedText.Params.USABLE: 7371 // pass through 7372 } 7373 } else if (type == BufferType.SPANNABLE || mMovement != null) { 7374 text = mSpannableFactory.newSpannable(text); 7375 } else if (!(text instanceof CharWrapper)) { 7376 text = TextUtils.stringOrSpannedString(text); 7377 } 7378 7379 @AccessibilityUtils.A11yTextChangeType int a11yTextChangeType = AccessibilityUtils.NONE; 7380 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 7381 a11yTextChangeType = AccessibilityUtils.textOrSpanChanged(text, mText); 7382 } 7383 7384 if (mAutoLinkMask != 0) { 7385 Spannable s2; 7386 7387 if (type == BufferType.EDITABLE || text instanceof Spannable) { 7388 s2 = (Spannable) text; 7389 } else { 7390 s2 = mSpannableFactory.newSpannable(text); 7391 } 7392 7393 if (Linkify.addLinks(s2, mAutoLinkMask)) { 7394 text = s2; 7395 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 7396 7397 /* 7398 * We must go ahead and set the text before changing the 7399 * movement method, because setMovementMethod() may call 7400 * setText() again to try to upgrade the buffer type. 7401 */ 7402 setTextInternal(text); 7403 if (a11yTextChangeType == AccessibilityUtils.NONE) { 7404 a11yTextChangeType = AccessibilityUtils.PARCELABLE_SPAN; 7405 } 7406 7407 // Do not change the movement method for text that support text selection as it 7408 // would prevent an arbitrary cursor displacement. 7409 if (mLinksClickable && !textCanBeSelected()) { 7410 setMovementMethod(LinkMovementMethod.getInstance()); 7411 } 7412 } 7413 } 7414 7415 mBufferType = type; 7416 setTextInternal(text); 7417 7418 if (mTransformation == null) { 7419 mTransformed = text; 7420 } else { 7421 mTransformed = mTransformation.getTransformation(text, this); 7422 } 7423 if (mTransformed == null) { 7424 // Should not happen if the transformation method follows the non-null postcondition. 7425 mTransformed = ""; 7426 } 7427 7428 final int textLength = text.length(); 7429 final boolean isOffsetMapping = mTransformed instanceof OffsetMapping; 7430 7431 if (text instanceof Spannable && (!mAllowTransformationLengthChange || isOffsetMapping)) { 7432 Spannable sp = (Spannable) text; 7433 7434 // Remove any ChangeWatchers that might have come from other TextViews. 7435 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 7436 final int count = watchers.length; 7437 for (int i = 0; i < count; i++) { 7438 sp.removeSpan(watchers[i]); 7439 } 7440 7441 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); 7442 7443 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE 7444 | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 7445 7446 if (mEditor != null) mEditor.addSpanWatchers(sp); 7447 7448 if (mTransformation != null) { 7449 final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0; 7450 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE 7451 | (priority << Spanned.SPAN_PRIORITY_SHIFT)); 7452 } 7453 7454 if (mMovement != null) { 7455 mMovement.initialize(this, (Spannable) text); 7456 7457 /* 7458 * Initializing the movement method will have set the 7459 * selection, so reset mSelectionMoved to keep that from 7460 * interfering with the normal on-focus selection-setting. 7461 */ 7462 if (mEditor != null) mEditor.mSelectionMoved = false; 7463 } 7464 } 7465 7466 if (mLayout != null) { 7467 checkForRelayout(); 7468 } 7469 7470 sendOnTextChanged(text, 0, oldlen, textLength); 7471 onTextChanged(text, 0, oldlen, textLength); 7472 7473 mHideHint = false; 7474 7475 if (a11yTextChangeType == AccessibilityUtils.TEXT) { 7476 notifyViewAccessibilityStateChangedIfNeeded( 7477 AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); 7478 } else if (a11yTextChangeType == AccessibilityUtils.PARCELABLE_SPAN) { 7479 notifyViewAccessibilityStateChangedIfNeeded( 7480 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 7481 } 7482 7483 if (needEditableForNotification) { 7484 sendAfterTextChanged((Editable) text); 7485 } else { 7486 notifyListeningManagersAfterTextChanged(); 7487 } 7488 7489 if (mEditor != null) { 7490 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 7491 mEditor.prepareCursorControllers(); 7492 7493 mEditor.maybeFireScheduledRestartInputForSetText(); 7494 } 7495 } 7496 7497 /** 7498 * Sets the TextView to display the specified slice of the specified 7499 * char array. You must promise that you will not change the contents 7500 * of the array except for right before another call to setText(), 7501 * since the TextView has no way to know that the text 7502 * has changed and that it needs to invalidate and re-layout. 7503 * 7504 * @throws NullPointerException if text is null 7505 * @throws IndexOutOfBoundsException if start or start+len are not in 0 to text.length 7506 * 7507 * @param text char array to be displayed 7508 * @param start start index in the char array 7509 * @param len length of char count after {@code start} 7510 */ 7511 public final void setText(@NonNull char[] text, int start, int len) { 7512 int oldlen = 0; 7513 7514 if (start < 0 || len < 0 || start + len > text.length) { 7515 throw new IndexOutOfBoundsException(start + ", " + len); 7516 } 7517 7518 /* 7519 * We must do the before-notification here ourselves because if 7520 * the old text is a CharWrapper we destroy it before calling 7521 * into the normal path. 7522 */ 7523 if (mText != null) { 7524 oldlen = mText.length(); 7525 sendBeforeTextChanged(mText, 0, oldlen, len); 7526 } else { 7527 sendBeforeTextChanged("", 0, 0, len); 7528 } 7529 7530 if (mCharWrapper == null) { 7531 mCharWrapper = new CharWrapper(text, start, len); 7532 } else { 7533 mCharWrapper.set(text, start, len); 7534 } 7535 7536 setText(mCharWrapper, mBufferType, false, oldlen); 7537 } 7538 7539 /** 7540 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains 7541 * the cursor position. Same as 7542 * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor 7543 * position (if any) is retained in the new text. 7544 * <p/> 7545 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 7546 * intermediate {@link Spannable Spannables}. Likewise it will use 7547 * {@link android.text.Editable.Factory} to create final or intermediate 7548 * {@link Editable Editables}. 7549 * 7550 * @param text text to be displayed 7551 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 7552 * stored as a static text, styleable/spannable text, or editable text 7553 * 7554 * @see #setText(CharSequence, android.widget.TextView.BufferType) 7555 */ 7556 public final void setTextKeepState(CharSequence text, BufferType type) { 7557 int start = getSelectionStart(); 7558 int end = getSelectionEnd(); 7559 int len = text.length(); 7560 7561 setText(text, type); 7562 7563 if (start >= 0 || end >= 0) { 7564 if (mSpannable != null) { 7565 Selection.setSelection(mSpannable, 7566 Math.max(0, Math.min(start, len)), 7567 Math.max(0, Math.min(end, len))); 7568 } 7569 } 7570 } 7571 7572 /** 7573 * Sets the text to be displayed using a string resource identifier. 7574 * 7575 * @param resid the resource identifier of the string resource to be displayed 7576 * 7577 * @see #setText(CharSequence) 7578 * 7579 * @attr ref android.R.styleable#TextView_text 7580 */ 7581 @android.view.RemotableViewMethod 7582 public final void setText(@StringRes int resid) { 7583 setText(getContext().getResources().getText(resid)); 7584 mTextSetFromXmlOrResourceId = true; 7585 mTextId = resid; 7586 } 7587 7588 /** 7589 * Sets the text to be displayed using a string resource identifier and the 7590 * {@link android.widget.TextView.BufferType}. 7591 * <p/> 7592 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 7593 * intermediate {@link Spannable Spannables}. Likewise it will use 7594 * {@link android.text.Editable.Factory} to create final or intermediate 7595 * {@link Editable Editables}. 7596 * 7597 * @param resid the resource identifier of the string resource to be displayed 7598 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 7599 * stored as a static text, styleable/spannable text, or editable text 7600 * 7601 * @see #setText(int) 7602 * @see #setText(CharSequence) 7603 * @see android.widget.TextView.BufferType 7604 * @see #setSpannableFactory(Spannable.Factory) 7605 * @see #setEditableFactory(Editable.Factory) 7606 * 7607 * @attr ref android.R.styleable#TextView_text 7608 * @attr ref android.R.styleable#TextView_bufferType 7609 */ 7610 public final void setText(@StringRes int resid, BufferType type) { 7611 setText(getContext().getResources().getText(resid), type); 7612 mTextSetFromXmlOrResourceId = true; 7613 mTextId = resid; 7614 } 7615 7616 /** 7617 * Sets the text to be displayed when the text of the TextView is empty. 7618 * Null means to use the normal empty text. The hint does not currently 7619 * participate in determining the size of the view. 7620 * 7621 * @attr ref android.R.styleable#TextView_hint 7622 */ 7623 @android.view.RemotableViewMethod 7624 public final void setHint(CharSequence hint) { 7625 setHintInternal(hint); 7626 7627 if (mEditor != null && isInputMethodTarget()) { 7628 mEditor.reportExtractedText(); 7629 } 7630 } 7631 7632 private void setHintInternal(CharSequence hint) { 7633 mHideHint = false; 7634 mHint = TextUtils.stringOrSpannedString(hint); 7635 7636 if (mLayout != null) { 7637 checkForRelayout(); 7638 } 7639 7640 if (mText.length() == 0) { 7641 invalidate(); 7642 } 7643 7644 // Invalidate display list if hint is currently used 7645 if (mEditor != null && mText.length() == 0 && mHint != null) { 7646 mEditor.invalidateTextDisplayList(); 7647 } 7648 } 7649 7650 /** 7651 * Sets the text to be displayed when the text of the TextView is empty, 7652 * from a resource. 7653 * 7654 * @attr ref android.R.styleable#TextView_hint 7655 */ 7656 @android.view.RemotableViewMethod 7657 public final void setHint(@StringRes int resid) { 7658 mHintId = resid; 7659 setHint(getContext().getResources().getText(resid)); 7660 } 7661 7662 /** 7663 * Returns the hint that is displayed when the text of the TextView 7664 * is empty. 7665 * 7666 * @attr ref android.R.styleable#TextView_hint 7667 */ 7668 @InspectableProperty 7669 @ViewDebug.CapturedViewProperty 7670 public CharSequence getHint() { 7671 return mHint; 7672 } 7673 7674 /** 7675 * Temporarily hides the hint text until the text is modified, or the hint text is modified, or 7676 * the view gains or loses focus. 7677 * 7678 * @hide 7679 */ 7680 public void hideHint() { 7681 if (isShowingHint()) { 7682 mHideHint = true; 7683 invalidate(); 7684 } 7685 } 7686 7687 /** 7688 * Returns if the text is constrained to a single horizontally scrolling line ignoring new 7689 * line characters instead of letting it wrap onto multiple lines. 7690 * 7691 * @attr ref android.R.styleable#TextView_singleLine 7692 */ 7693 @InspectableProperty 7694 public boolean isSingleLine() { 7695 return mSingleLine; 7696 } 7697 7698 private static boolean isMultilineInputType(int type) { 7699 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) 7700 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 7701 } 7702 7703 /** 7704 * Removes the suggestion spans. 7705 */ 7706 CharSequence removeSuggestionSpans(CharSequence text) { 7707 if (text instanceof Spanned) { 7708 Spannable spannable; 7709 if (text instanceof Spannable) { 7710 spannable = (Spannable) text; 7711 } else { 7712 spannable = mSpannableFactory.newSpannable(text); 7713 } 7714 7715 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 7716 if (spans.length == 0) { 7717 return text; 7718 } else { 7719 text = spannable; 7720 } 7721 7722 for (int i = 0; i < spans.length; i++) { 7723 spannable.removeSpan(spans[i]); 7724 } 7725 } 7726 return text; 7727 } 7728 7729 /** 7730 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 7731 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 7732 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 7733 * then a soft keyboard will not be displayed for this text view. 7734 * 7735 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 7736 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 7737 * type. 7738 * 7739 * @see #getInputType() 7740 * @see #setRawInputType(int) 7741 * @see android.text.InputType 7742 * @attr ref android.R.styleable#TextView_inputType 7743 */ 7744 public void setInputType(int type) { 7745 final boolean wasPassword = isPasswordInputType(getInputType()); 7746 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 7747 setInputType(type, false); 7748 final boolean isPassword = isPasswordInputType(type); 7749 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 7750 boolean forceUpdate = false; 7751 if (isPassword) { 7752 setTransformationMethod(PasswordTransformationMethod.getInstance()); 7753 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 7754 Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED); 7755 } else if (isVisiblePassword) { 7756 if (mTransformation == PasswordTransformationMethod.getInstance()) { 7757 forceUpdate = true; 7758 } 7759 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 7760 Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED); 7761 } else if (wasPassword || wasVisiblePassword) { 7762 // not in password mode, clean up typeface and transformation 7763 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, 7764 DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL, 7765 FontStyle.FONT_WEIGHT_UNSPECIFIED); 7766 if (mTransformation == PasswordTransformationMethod.getInstance()) { 7767 forceUpdate = true; 7768 } 7769 } 7770 7771 boolean singleLine = !isMultilineInputType(type); 7772 7773 // We need to update the single line mode if it has changed or we 7774 // were previously in password mode. 7775 if (mSingleLine != singleLine || forceUpdate) { 7776 // Change single line mode, but only change the transformation if 7777 // we are not in password mode. 7778 applySingleLine(singleLine, !isPassword, true, true); 7779 } 7780 7781 if (!isSuggestionsEnabled()) { 7782 setTextInternal(removeSuggestionSpans(mText)); 7783 } 7784 7785 InputMethodManager imm = getInputMethodManager(); 7786 if (imm != null) imm.restartInput(this); 7787 } 7788 7789 /** 7790 * It would be better to rely on the input type for everything. A password inputType should have 7791 * a password transformation. We should hence use isPasswordInputType instead of this method. 7792 * 7793 * We should: 7794 * - Call setInputType in setKeyListener instead of changing the input type directly (which 7795 * would install the correct transformation). 7796 * - Refuse the installation of a non-password transformation in setTransformation if the input 7797 * type is password. 7798 * 7799 * However, this is like this for legacy reasons and we cannot break existing apps. This method 7800 * is useful since it matches what the user can see (obfuscated text or not). 7801 * 7802 * @return true if the current transformation method is of the password type. 7803 */ 7804 boolean hasPasswordTransformationMethod() { 7805 return mTransformation instanceof PasswordTransformationMethod; 7806 } 7807 7808 /** 7809 * Returns true if the current inputType is any type of password. 7810 * 7811 * @hide 7812 */ 7813 public boolean isAnyPasswordInputType() { 7814 final int inputType = getInputType(); 7815 return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType); 7816 } 7817 7818 static boolean isPasswordInputType(int inputType) { 7819 final int variation = 7820 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 7821 return variation 7822 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 7823 || variation 7824 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 7825 || variation 7826 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 7827 } 7828 7829 private static boolean isVisiblePasswordInputType(int inputType) { 7830 final int variation = 7831 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 7832 return variation 7833 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 7834 } 7835 7836 /** 7837 * Directly change the content type integer of the text view, without 7838 * modifying any other state. 7839 * @see #setInputType(int) 7840 * @see android.text.InputType 7841 * @attr ref android.R.styleable#TextView_inputType 7842 */ 7843 public void setRawInputType(int type) { 7844 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 7845 createEditorIfNeeded(); 7846 mEditor.mInputType = type; 7847 } 7848 7849 @Override 7850 public String[] getAutofillHints() { 7851 String[] hints = super.getAutofillHints(); 7852 if (isAnyPasswordInputType()) { 7853 if (!ArrayUtils.contains(hints, AUTOFILL_HINT_PASSWORD_AUTO)) { 7854 hints = ArrayUtils.appendElement(String.class, hints, 7855 AUTOFILL_HINT_PASSWORD_AUTO); 7856 } 7857 } 7858 return hints; 7859 } 7860 7861 /** 7862 * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise 7863 * a {@code Locale} object that can be used to customize key various listeners. 7864 * @see DateKeyListener#getInstance(Locale) 7865 * @see DateTimeKeyListener#getInstance(Locale) 7866 * @see DigitsKeyListener#getInstance(Locale) 7867 * @see TimeKeyListener#getInstance(Locale) 7868 */ 7869 @Nullable 7870 private Locale getCustomLocaleForKeyListenerOrNull() { 7871 if (!mUseInternationalizedInput) { 7872 // If the application does not target O, stick to the previous behavior. 7873 return null; 7874 } 7875 final LocaleList locales = getImeHintLocales(); 7876 if (locales == null) { 7877 // If the application does not explicitly specify IME hint locale, also stick to the 7878 // previous behavior. 7879 return null; 7880 } 7881 return locales.get(0); 7882 } 7883 7884 @UnsupportedAppUsage 7885 private void setInputType(int type, boolean direct) { 7886 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 7887 KeyListener input; 7888 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 7889 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 7890 TextKeyListener.Capitalize cap; 7891 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 7892 cap = TextKeyListener.Capitalize.CHARACTERS; 7893 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 7894 cap = TextKeyListener.Capitalize.WORDS; 7895 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 7896 cap = TextKeyListener.Capitalize.SENTENCES; 7897 } else { 7898 cap = TextKeyListener.Capitalize.NONE; 7899 } 7900 input = TextKeyListener.getInstance(autotext, cap); 7901 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 7902 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 7903 input = DigitsKeyListener.getInstance( 7904 locale, 7905 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 7906 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 7907 if (locale != null) { 7908 // Override type, if necessary for i18n. 7909 int newType = input.getInputType(); 7910 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS; 7911 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) { 7912 // The class is different from the original class. So we need to override 7913 // 'type'. But we want to keep the password flag if it's there. 7914 if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) { 7915 newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 7916 } 7917 type = newType; 7918 } 7919 } 7920 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 7921 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 7922 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 7923 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 7924 input = DateKeyListener.getInstance(locale); 7925 break; 7926 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 7927 input = TimeKeyListener.getInstance(locale); 7928 break; 7929 default: 7930 input = DateTimeKeyListener.getInstance(locale); 7931 break; 7932 } 7933 if (mUseInternationalizedInput) { 7934 type = input.getInputType(); // Override type, if necessary for i18n. 7935 } 7936 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 7937 input = DialerKeyListener.getInstance(); 7938 } else { 7939 input = TextKeyListener.getInstance(); 7940 } 7941 setRawInputType(type); 7942 mListenerChanged = false; 7943 if (direct) { 7944 createEditorIfNeeded(); 7945 mEditor.mKeyListener = input; 7946 } else { 7947 setKeyListenerOnly(input); 7948 } 7949 } 7950 7951 /** 7952 * Get the type of the editable content. 7953 * 7954 * @see #setInputType(int) 7955 * @see android.text.InputType 7956 */ 7957 @InspectableProperty(flagMapping = { 7958 @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL), 7959 @FlagEntry( 7960 name = "text", 7961 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7962 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL), 7963 @FlagEntry( 7964 name = "textUri", 7965 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7966 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI), 7967 @FlagEntry( 7968 name = "textEmailAddress", 7969 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7970 target = InputType.TYPE_CLASS_TEXT 7971 | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS), 7972 @FlagEntry( 7973 name = "textEmailSubject", 7974 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7975 target = InputType.TYPE_CLASS_TEXT 7976 | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT), 7977 @FlagEntry( 7978 name = "textShortMessage", 7979 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7980 target = InputType.TYPE_CLASS_TEXT 7981 | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE), 7982 @FlagEntry( 7983 name = "textLongMessage", 7984 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7985 target = InputType.TYPE_CLASS_TEXT 7986 | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE), 7987 @FlagEntry( 7988 name = "textPersonName", 7989 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7990 target = InputType.TYPE_CLASS_TEXT 7991 | InputType.TYPE_TEXT_VARIATION_PERSON_NAME), 7992 @FlagEntry( 7993 name = "textPostalAddress", 7994 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7995 target = InputType.TYPE_CLASS_TEXT 7996 | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS), 7997 @FlagEntry( 7998 name = "textPassword", 7999 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8000 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD), 8001 @FlagEntry( 8002 name = "textVisiblePassword", 8003 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8004 target = InputType.TYPE_CLASS_TEXT 8005 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD), 8006 @FlagEntry( 8007 name = "textWebEditText", 8008 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8009 target = InputType.TYPE_CLASS_TEXT 8010 | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT), 8011 @FlagEntry( 8012 name = "textFilter", 8013 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8014 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER), 8015 @FlagEntry( 8016 name = "textPhonetic", 8017 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8018 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC), 8019 @FlagEntry( 8020 name = "textWebEmailAddress", 8021 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8022 target = InputType.TYPE_CLASS_TEXT 8023 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS), 8024 @FlagEntry( 8025 name = "textWebPassword", 8026 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8027 target = InputType.TYPE_CLASS_TEXT 8028 | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD), 8029 @FlagEntry( 8030 name = "number", 8031 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8032 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL), 8033 @FlagEntry( 8034 name = "numberPassword", 8035 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8036 target = InputType.TYPE_CLASS_NUMBER 8037 | InputType.TYPE_NUMBER_VARIATION_PASSWORD), 8038 @FlagEntry( 8039 name = "phone", 8040 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8041 target = InputType.TYPE_CLASS_PHONE), 8042 @FlagEntry( 8043 name = "datetime", 8044 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8045 target = InputType.TYPE_CLASS_DATETIME 8046 | InputType.TYPE_DATETIME_VARIATION_NORMAL), 8047 @FlagEntry( 8048 name = "date", 8049 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8050 target = InputType.TYPE_CLASS_DATETIME 8051 | InputType.TYPE_DATETIME_VARIATION_DATE), 8052 @FlagEntry( 8053 name = "time", 8054 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8055 target = InputType.TYPE_CLASS_DATETIME 8056 | InputType.TYPE_DATETIME_VARIATION_TIME), 8057 @FlagEntry( 8058 name = "textCapCharacters", 8059 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8060 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS), 8061 @FlagEntry( 8062 name = "textCapWords", 8063 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8064 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS), 8065 @FlagEntry( 8066 name = "textCapSentences", 8067 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8068 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), 8069 @FlagEntry( 8070 name = "textAutoCorrect", 8071 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8072 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT), 8073 @FlagEntry( 8074 name = "textAutoComplete", 8075 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8076 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE), 8077 @FlagEntry( 8078 name = "textMultiLine", 8079 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8080 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE), 8081 @FlagEntry( 8082 name = "textImeMultiLine", 8083 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8084 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE), 8085 @FlagEntry( 8086 name = "textNoSuggestions", 8087 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8088 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS), 8089 @FlagEntry( 8090 name = "numberSigned", 8091 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8092 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED), 8093 @FlagEntry( 8094 name = "numberDecimal", 8095 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8096 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL), 8097 }) 8098 public int getInputType() { 8099 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 8100 } 8101 8102 /** 8103 * Change the editor type integer associated with the text view, which 8104 * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions} 8105 * when it has focus. 8106 * @see #getImeOptions 8107 * @see android.view.inputmethod.EditorInfo 8108 * @attr ref android.R.styleable#TextView_imeOptions 8109 */ 8110 public void setImeOptions(int imeOptions) { 8111 createEditorIfNeeded(); 8112 mEditor.createInputContentTypeIfNeeded(); 8113 mEditor.mInputContentType.imeOptions = imeOptions; 8114 } 8115 8116 /** 8117 * Get the type of the Input Method Editor (IME). 8118 * @return the type of the IME 8119 * @see #setImeOptions(int) 8120 * @see EditorInfo 8121 */ 8122 @InspectableProperty(flagMapping = { 8123 @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL), 8124 @FlagEntry( 8125 name = "actionUnspecified", 8126 mask = EditorInfo.IME_MASK_ACTION, 8127 target = EditorInfo.IME_ACTION_UNSPECIFIED), 8128 @FlagEntry( 8129 name = "actionNone", 8130 mask = EditorInfo.IME_MASK_ACTION, 8131 target = EditorInfo.IME_ACTION_NONE), 8132 @FlagEntry( 8133 name = "actionGo", 8134 mask = EditorInfo.IME_MASK_ACTION, 8135 target = EditorInfo.IME_ACTION_GO), 8136 @FlagEntry( 8137 name = "actionSearch", 8138 mask = EditorInfo.IME_MASK_ACTION, 8139 target = EditorInfo.IME_ACTION_SEARCH), 8140 @FlagEntry( 8141 name = "actionSend", 8142 mask = EditorInfo.IME_MASK_ACTION, 8143 target = EditorInfo.IME_ACTION_SEND), 8144 @FlagEntry( 8145 name = "actionNext", 8146 mask = EditorInfo.IME_MASK_ACTION, 8147 target = EditorInfo.IME_ACTION_NEXT), 8148 @FlagEntry( 8149 name = "actionDone", 8150 mask = EditorInfo.IME_MASK_ACTION, 8151 target = EditorInfo.IME_ACTION_DONE), 8152 @FlagEntry( 8153 name = "actionPrevious", 8154 mask = EditorInfo.IME_MASK_ACTION, 8155 target = EditorInfo.IME_ACTION_PREVIOUS), 8156 @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII), 8157 @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT), 8158 @FlagEntry( 8159 name = "flagNavigatePrevious", 8160 target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS), 8161 @FlagEntry( 8162 name = "flagNoAccessoryAction", 8163 target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION), 8164 @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION), 8165 @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI), 8166 @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN), 8167 @FlagEntry( 8168 name = "flagNoPersonalizedLearning", 8169 target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING), 8170 }) 8171 public int getImeOptions() { 8172 return mEditor != null && mEditor.mInputContentType != null 8173 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 8174 } 8175 8176 /** 8177 * Change the custom IME action associated with the text view, which 8178 * will be reported to an IME with {@link EditorInfo#actionLabel} 8179 * and {@link EditorInfo#actionId} when it has focus. 8180 * @see #getImeActionLabel 8181 * @see #getImeActionId 8182 * @see android.view.inputmethod.EditorInfo 8183 * @attr ref android.R.styleable#TextView_imeActionLabel 8184 * @attr ref android.R.styleable#TextView_imeActionId 8185 */ 8186 public void setImeActionLabel(CharSequence label, int actionId) { 8187 createEditorIfNeeded(); 8188 mEditor.createInputContentTypeIfNeeded(); 8189 mEditor.mInputContentType.imeActionLabel = label; 8190 mEditor.mInputContentType.imeActionId = actionId; 8191 } 8192 8193 /** 8194 * Get the IME action label previous set with {@link #setImeActionLabel}. 8195 * 8196 * @see #setImeActionLabel 8197 * @see android.view.inputmethod.EditorInfo 8198 */ 8199 @InspectableProperty 8200 public CharSequence getImeActionLabel() { 8201 return mEditor != null && mEditor.mInputContentType != null 8202 ? mEditor.mInputContentType.imeActionLabel : null; 8203 } 8204 8205 /** 8206 * Get the IME action ID previous set with {@link #setImeActionLabel}. 8207 * 8208 * @see #setImeActionLabel 8209 * @see android.view.inputmethod.EditorInfo 8210 */ 8211 @InspectableProperty 8212 public int getImeActionId() { 8213 return mEditor != null && mEditor.mInputContentType != null 8214 ? mEditor.mInputContentType.imeActionId : 0; 8215 } 8216 8217 /** 8218 * Set a special listener to be called when an action is performed 8219 * on the text view. This will be called when the enter key is pressed, 8220 * or when an action supplied to the IME is selected by the user. Setting 8221 * this means that the normal hard key event will not insert a newline 8222 * into the text view, even if it is multi-line; holding down the ALT 8223 * modifier will, however, allow the user to insert a newline character. 8224 */ 8225 public void setOnEditorActionListener(OnEditorActionListener l) { 8226 createEditorIfNeeded(); 8227 mEditor.createInputContentTypeIfNeeded(); 8228 mEditor.mInputContentType.onEditorActionListener = l; 8229 } 8230 8231 /** 8232 * Called when an attached input method calls 8233 * {@link InputConnection#performEditorAction(int) 8234 * InputConnection.performEditorAction()} 8235 * for this text view. The default implementation will call your action 8236 * listener supplied to {@link #setOnEditorActionListener}, or perform 8237 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 8238 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 8239 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 8240 * EditorInfo.IME_ACTION_DONE}. 8241 * 8242 * <p>For backwards compatibility, if no IME options have been set and the 8243 * text view would not normally advance focus on enter, then 8244 * the NEXT and DONE actions received here will be turned into an enter 8245 * key down/up pair to go through the normal key handling. 8246 * 8247 * @param actionCode The code of the action being performed. 8248 * 8249 * @see #setOnEditorActionListener 8250 */ 8251 public void onEditorAction(int actionCode) { 8252 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 8253 if (ict != null) { 8254 if (ict.onEditorActionListener != null) { 8255 if (ict.onEditorActionListener.onEditorAction(this, 8256 actionCode, null)) { 8257 return; 8258 } 8259 } 8260 8261 // This is the handling for some default action. 8262 // Note that for backwards compatibility we don't do this 8263 // default handling if explicit ime options have not been given, 8264 // instead turning this into the normal enter key codes that an 8265 // app may be expecting. 8266 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 8267 View v = focusSearch(FOCUS_FORWARD); 8268 if (v != null) { 8269 if (!v.requestFocus(FOCUS_FORWARD)) { 8270 throw new IllegalStateException("focus search returned a view " 8271 + "that wasn't able to take focus!"); 8272 } 8273 } 8274 return; 8275 8276 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 8277 View v = focusSearch(FOCUS_BACKWARD); 8278 if (v != null) { 8279 if (!v.requestFocus(FOCUS_BACKWARD)) { 8280 throw new IllegalStateException("focus search returned a view " 8281 + "that wasn't able to take focus!"); 8282 } 8283 } 8284 return; 8285 8286 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 8287 InputMethodManager imm = getInputMethodManager(); 8288 if (imm != null) { 8289 imm.hideSoftInputFromView(this, 0); 8290 } 8291 return; 8292 } 8293 } 8294 8295 ViewRootImpl viewRootImpl = getViewRootImpl(); 8296 if (viewRootImpl != null) { 8297 long eventTime = SystemClock.uptimeMillis(); 8298 viewRootImpl.dispatchKeyFromIme( 8299 new KeyEvent(eventTime, eventTime, 8300 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 8301 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 8302 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 8303 | KeyEvent.FLAG_EDITOR_ACTION)); 8304 viewRootImpl.dispatchKeyFromIme( 8305 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 8306 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 8307 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 8308 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 8309 | KeyEvent.FLAG_EDITOR_ACTION)); 8310 } 8311 } 8312 8313 /** 8314 * Set the private content type of the text, which is the 8315 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 8316 * field that will be filled in when creating an input connection. 8317 * 8318 * @see #getPrivateImeOptions() 8319 * @see EditorInfo#privateImeOptions 8320 * @attr ref android.R.styleable#TextView_privateImeOptions 8321 */ 8322 public void setPrivateImeOptions(String type) { 8323 createEditorIfNeeded(); 8324 mEditor.createInputContentTypeIfNeeded(); 8325 mEditor.mInputContentType.privateImeOptions = type; 8326 } 8327 8328 /** 8329 * Get the private type of the content. 8330 * 8331 * @see #setPrivateImeOptions(String) 8332 * @see EditorInfo#privateImeOptions 8333 */ 8334 @InspectableProperty 8335 public String getPrivateImeOptions() { 8336 return mEditor != null && mEditor.mInputContentType != null 8337 ? mEditor.mInputContentType.privateImeOptions : null; 8338 } 8339 8340 /** 8341 * Set the extra input data of the text, which is the 8342 * {@link EditorInfo#extras TextBoxAttribute.extras} 8343 * Bundle that will be filled in when creating an input connection. The 8344 * given integer is the resource identifier of an XML resource holding an 8345 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 8346 * 8347 * @see #getInputExtras(boolean) 8348 * @see EditorInfo#extras 8349 * @attr ref android.R.styleable#TextView_editorExtras 8350 */ 8351 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException { 8352 createEditorIfNeeded(); 8353 XmlResourceParser parser = getResources().getXml(xmlResId); 8354 mEditor.createInputContentTypeIfNeeded(); 8355 mEditor.mInputContentType.extras = new Bundle(); 8356 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 8357 } 8358 8359 /** 8360 * Retrieve the input extras currently associated with the text view, which 8361 * can be viewed as well as modified. 8362 * 8363 * @param create If true, the extras will be created if they don't already 8364 * exist. Otherwise, null will be returned if none have been created. 8365 * @see #setInputExtras(int) 8366 * @see EditorInfo#extras 8367 * @attr ref android.R.styleable#TextView_editorExtras 8368 */ 8369 public Bundle getInputExtras(boolean create) { 8370 if (mEditor == null && !create) return null; 8371 createEditorIfNeeded(); 8372 if (mEditor.mInputContentType == null) { 8373 if (!create) return null; 8374 mEditor.createInputContentTypeIfNeeded(); 8375 } 8376 if (mEditor.mInputContentType.extras == null) { 8377 if (!create) return null; 8378 mEditor.mInputContentType.extras = new Bundle(); 8379 } 8380 return mEditor.mInputContentType.extras; 8381 } 8382 8383 /** 8384 * Change "hint" locales associated with the text view, which will be reported to an IME with 8385 * {@link EditorInfo#hintLocales} when it has focus. 8386 * 8387 * Starting with Android O, this also causes internationalized listeners to be created (or 8388 * change locale) based on the first locale in the input locale list. 8389 * 8390 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to 8391 * call {@link InputMethodManager#restartInput(View)}.</p> 8392 * @param hintLocales List of the languages that the user is supposed to switch to no matter 8393 * what input method subtype is currently used. Set {@code null} to clear the current "hint". 8394 * @see #getImeHintLocales() 8395 * @see android.view.inputmethod.EditorInfo#hintLocales 8396 */ 8397 public void setImeHintLocales(@Nullable LocaleList hintLocales) { 8398 createEditorIfNeeded(); 8399 mEditor.createInputContentTypeIfNeeded(); 8400 mEditor.mInputContentType.imeHintLocales = hintLocales; 8401 if (mUseInternationalizedInput) { 8402 changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0)); 8403 } 8404 } 8405 8406 /** 8407 * @return The current languages list "hint". {@code null} when no "hint" is available. 8408 * @see #setImeHintLocales(LocaleList) 8409 * @see android.view.inputmethod.EditorInfo#hintLocales 8410 */ 8411 @Nullable 8412 public LocaleList getImeHintLocales() { 8413 if (mEditor == null) { 8414 return null; 8415 } 8416 if (mEditor.mInputContentType == null) { 8417 return null; 8418 } 8419 return mEditor.mInputContentType.imeHintLocales; 8420 } 8421 8422 /** 8423 * Returns the error message that was set to be displayed with 8424 * {@link #setError}, or <code>null</code> if no error was set 8425 * or if it the error was cleared by the widget after user input. 8426 */ 8427 public CharSequence getError() { 8428 return mEditor == null ? null : mEditor.mError; 8429 } 8430 8431 /** 8432 * Sets the right-hand compound drawable of the TextView to the "error" 8433 * icon and sets an error message that will be displayed in a popup when 8434 * the TextView has focus. The icon and error message will be reset to 8435 * null when any key events cause changes to the TextView's text. If the 8436 * <code>error</code> is <code>null</code>, the error message and icon 8437 * will be cleared. 8438 */ 8439 @android.view.RemotableViewMethod 8440 public void setError(CharSequence error) { 8441 if (error == null) { 8442 setError(null, null); 8443 } else { 8444 Drawable dr = getContext().getDrawable( 8445 com.android.internal.R.drawable.indicator_input_error); 8446 8447 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 8448 setError(error, dr); 8449 } 8450 } 8451 8452 /** 8453 * Sets the right-hand compound drawable of the TextView to the specified 8454 * icon and sets an error message that will be displayed in a popup when 8455 * the TextView has focus. The icon and error message will be reset to 8456 * null when any key events cause changes to the TextView's text. The 8457 * drawable must already have had {@link Drawable#setBounds} set on it. 8458 * If the <code>error</code> is <code>null</code>, the error message will 8459 * be cleared (and you should provide a <code>null</code> icon as well). 8460 */ 8461 public void setError(CharSequence error, Drawable icon) { 8462 createEditorIfNeeded(); 8463 mEditor.setError(error, icon); 8464 notifyViewAccessibilityStateChangedIfNeeded( 8465 AccessibilityEvent.CONTENT_CHANGE_TYPE_ERROR 8466 | AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_INVALID); 8467 } 8468 8469 @Override 8470 protected boolean setFrame(int l, int t, int r, int b) { 8471 boolean result = super.setFrame(l, t, r, b); 8472 8473 if (mEditor != null) mEditor.setFrame(); 8474 8475 restartMarqueeIfNeeded(); 8476 8477 return result; 8478 } 8479 8480 private void restartMarqueeIfNeeded() { 8481 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 8482 mRestartMarquee = false; 8483 startMarquee(); 8484 } 8485 } 8486 8487 /** 8488 * Sets the list of input filters that will be used if the buffer is 8489 * Editable. Has no effect otherwise. 8490 * 8491 * @attr ref android.R.styleable#TextView_maxLength 8492 */ 8493 public void setFilters(InputFilter[] filters) { 8494 if (filters == null) { 8495 throw new IllegalArgumentException(); 8496 } 8497 8498 mFilters = filters; 8499 8500 if (mText instanceof Editable) { 8501 setFilters((Editable) mText, filters); 8502 } 8503 } 8504 8505 /** 8506 * Sets the list of input filters on the specified Editable, 8507 * and includes mInput in the list if it is an InputFilter. 8508 */ 8509 private void setFilters(Editable e, InputFilter[] filters) { 8510 if (mEditor != null) { 8511 final boolean undoFilter = mEditor.mUndoInputFilter != null; 8512 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 8513 int num = 0; 8514 if (undoFilter) num++; 8515 if (keyFilter) num++; 8516 if (num > 0) { 8517 InputFilter[] nf = new InputFilter[filters.length + num]; 8518 8519 System.arraycopy(filters, 0, nf, 0, filters.length); 8520 num = 0; 8521 if (undoFilter) { 8522 nf[filters.length] = mEditor.mUndoInputFilter; 8523 num++; 8524 } 8525 if (keyFilter) { 8526 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 8527 } 8528 8529 e.setFilters(nf); 8530 return; 8531 } 8532 } 8533 e.setFilters(filters); 8534 } 8535 8536 /** 8537 * Returns the current list of input filters. 8538 * 8539 * @attr ref android.R.styleable#TextView_maxLength 8540 */ 8541 public InputFilter[] getFilters() { 8542 return mFilters; 8543 } 8544 8545 ///////////////////////////////////////////////////////////////////////// 8546 8547 private int getBoxHeight(Layout l) { 8548 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 8549 int padding = (l == mHintLayout) 8550 ? getCompoundPaddingTop() + getCompoundPaddingBottom() 8551 : getExtendedPaddingTop() + getExtendedPaddingBottom(); 8552 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 8553 } 8554 8555 @UnsupportedAppUsage 8556 int getVerticalOffset(boolean forceNormal) { 8557 int voffset = 0; 8558 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 8559 8560 Layout l = mLayout; 8561 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 8562 l = mHintLayout; 8563 } 8564 8565 if (gravity != Gravity.TOP) { 8566 int boxht = getBoxHeight(l); 8567 int textht = l.getHeight(); 8568 8569 if (textht < boxht) { 8570 if (gravity == Gravity.BOTTOM) { 8571 voffset = boxht - textht; 8572 } else { // (gravity == Gravity.CENTER_VERTICAL) 8573 voffset = (boxht - textht) >> 1; 8574 } 8575 } 8576 } 8577 return voffset; 8578 } 8579 8580 private int getBottomVerticalOffset(boolean forceNormal) { 8581 int voffset = 0; 8582 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 8583 8584 Layout l = mLayout; 8585 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 8586 l = mHintLayout; 8587 } 8588 8589 if (gravity != Gravity.BOTTOM) { 8590 int boxht = getBoxHeight(l); 8591 int textht = l.getHeight(); 8592 8593 if (textht < boxht) { 8594 if (gravity == Gravity.TOP) { 8595 voffset = boxht - textht; 8596 } else { // (gravity == Gravity.CENTER_VERTICAL) 8597 voffset = (boxht - textht) >> 1; 8598 } 8599 } 8600 } 8601 return voffset; 8602 } 8603 8604 void invalidateCursorPath() { 8605 if (mHighlightPathBogus) { 8606 invalidateCursor(); 8607 } else { 8608 final int horizontalPadding = getCompoundPaddingLeft(); 8609 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 8610 8611 if (mEditor.mDrawableForCursor == null) { 8612 synchronized (TEMP_RECTF) { 8613 /* 8614 * The reason for this concern about the thickness of the 8615 * cursor and doing the floor/ceil on the coordinates is that 8616 * some EditTexts (notably textfields in the Browser) have 8617 * anti-aliased text where not all the characters are 8618 * necessarily at integer-multiple locations. This should 8619 * make sure the entire cursor gets invalidated instead of 8620 * sometimes missing half a pixel. 8621 */ 8622 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); 8623 if (thick < 1.0f) { 8624 thick = 1.0f; 8625 } 8626 8627 thick /= 2.0f; 8628 8629 // mHighlightPath is guaranteed to be non null at that point. 8630 mHighlightPath.computeBounds(TEMP_RECTF, false); 8631 8632 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), 8633 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), 8634 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), 8635 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 8636 } 8637 } else { 8638 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 8639 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 8640 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 8641 } 8642 } 8643 } 8644 8645 void invalidateCursor() { 8646 int where = getSelectionEnd(); 8647 8648 invalidateCursor(where, where, where); 8649 } 8650 8651 private void invalidateCursor(int a, int b, int c) { 8652 if (a >= 0 || b >= 0 || c >= 0) { 8653 int start = Math.min(Math.min(a, b), c); 8654 int end = Math.max(Math.max(a, b), c); 8655 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 8656 } 8657 } 8658 8659 /** 8660 * Invalidates the region of text enclosed between the start and end text offsets. 8661 */ 8662 void invalidateRegion(int start, int end, boolean invalidateCursor) { 8663 if (mLayout == null) { 8664 invalidate(); 8665 } else { 8666 start = originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR); 8667 end = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR); 8668 int lineStart = mLayout.getLineForOffset(start); 8669 int top = mLayout.getLineTop(lineStart); 8670 8671 // This is ridiculous, but the descent from the line above 8672 // can hang down into the line we really want to redraw, 8673 // so we have to invalidate part of the line above to make 8674 // sure everything that needs to be redrawn really is. 8675 // (But not the whole line above, because that would cause 8676 // the same problem with the descenders on the line above it!) 8677 if (lineStart > 0) { 8678 top -= mLayout.getLineDescent(lineStart - 1); 8679 } 8680 8681 int lineEnd; 8682 8683 if (start == end) { 8684 lineEnd = lineStart; 8685 } else { 8686 lineEnd = mLayout.getLineForOffset(end); 8687 } 8688 8689 int bottom = mLayout.getLineBottom(lineEnd); 8690 8691 // mEditor can be null in case selection is set programmatically. 8692 if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) { 8693 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 8694 top = Math.min(top, bounds.top); 8695 bottom = Math.max(bottom, bounds.bottom); 8696 } 8697 8698 final int compoundPaddingLeft = getCompoundPaddingLeft(); 8699 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 8700 8701 int left, right; 8702 if (lineStart == lineEnd && !invalidateCursor) { 8703 left = (int) mLayout.getPrimaryHorizontal(start); 8704 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 8705 left += compoundPaddingLeft; 8706 right += compoundPaddingLeft; 8707 } else { 8708 // Rectangle bounding box when the region spans several lines 8709 left = compoundPaddingLeft; 8710 right = getWidth() - getCompoundPaddingRight(); 8711 } 8712 8713 invalidate(mScrollX + left, verticalPadding + top, 8714 mScrollX + right, verticalPadding + bottom); 8715 } 8716 } 8717 8718 private void registerForPreDraw() { 8719 if (!mPreDrawRegistered) { 8720 getViewTreeObserver().addOnPreDrawListener(this); 8721 mPreDrawRegistered = true; 8722 } 8723 } 8724 8725 private void unregisterForPreDraw() { 8726 getViewTreeObserver().removeOnPreDrawListener(this); 8727 mPreDrawRegistered = false; 8728 mPreDrawListenerDetached = false; 8729 } 8730 8731 /** 8732 * {@inheritDoc} 8733 */ 8734 @Override 8735 public boolean onPreDraw() { 8736 if (mLayout == null) { 8737 assumeLayout(); 8738 } 8739 8740 if (mMovement != null) { 8741 /* This code also provides auto-scrolling when a cursor is moved using a 8742 * CursorController (insertion point or selection limits). 8743 * For selection, ensure start or end is visible depending on controller's state. 8744 */ 8745 int curs = getSelectionEnd(); 8746 // Do not create the controller if it is not already created. 8747 if (mEditor != null && mEditor.mSelectionModifierCursorController != null 8748 && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 8749 curs = getSelectionStart(); 8750 } 8751 8752 /* 8753 * TODO: This should really only keep the end in view if 8754 * it already was before the text changed. I'm not sure 8755 * of a good way to tell from here if it was. 8756 */ 8757 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 8758 curs = mText.length(); 8759 } 8760 8761 if (curs >= 0) { 8762 bringPointIntoView(curs); 8763 } 8764 } else { 8765 bringTextIntoView(); 8766 } 8767 8768 // This has to be checked here since: 8769 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 8770 // a screen rotation) since layout is not yet initialized at that point. 8771 if (mEditor != null && mEditor.mCreatedWithASelection) { 8772 mEditor.refreshTextActionMode(); 8773 mEditor.mCreatedWithASelection = false; 8774 } 8775 8776 unregisterForPreDraw(); 8777 8778 return true; 8779 } 8780 8781 @Override 8782 protected void onAttachedToWindow() { 8783 super.onAttachedToWindow(); 8784 8785 if (mEditor != null) mEditor.onAttachedToWindow(); 8786 8787 if (mPreDrawListenerDetached) { 8788 getViewTreeObserver().addOnPreDrawListener(this); 8789 mPreDrawListenerDetached = false; 8790 } 8791 } 8792 8793 /** @hide */ 8794 @Override 8795 protected void onDetachedFromWindowInternal() { 8796 if (mPreDrawRegistered) { 8797 getViewTreeObserver().removeOnPreDrawListener(this); 8798 mPreDrawListenerDetached = true; 8799 } 8800 8801 resetResolvedDrawables(); 8802 8803 if (mEditor != null) mEditor.onDetachedFromWindow(); 8804 8805 super.onDetachedFromWindowInternal(); 8806 } 8807 8808 @Override 8809 public void onScreenStateChanged(int screenState) { 8810 super.onScreenStateChanged(screenState); 8811 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 8812 } 8813 8814 @Override 8815 protected boolean isPaddingOffsetRequired() { 8816 return mShadowRadius != 0 || mDrawables != null; 8817 } 8818 8819 @Override 8820 protected int getLeftPaddingOffset() { 8821 return getCompoundPaddingLeft() - mPaddingLeft 8822 + (int) Math.min(0, mShadowDx - mShadowRadius); 8823 } 8824 8825 @Override 8826 protected int getTopPaddingOffset() { 8827 return (int) Math.min(0, mShadowDy - mShadowRadius); 8828 } 8829 8830 @Override 8831 protected int getBottomPaddingOffset() { 8832 return (int) Math.max(0, mShadowDy + mShadowRadius); 8833 } 8834 8835 @Override 8836 protected int getRightPaddingOffset() { 8837 return -(getCompoundPaddingRight() - mPaddingRight) 8838 + (int) Math.max(0, mShadowDx + mShadowRadius); 8839 } 8840 8841 @Override 8842 protected boolean verifyDrawable(@NonNull Drawable who) { 8843 final boolean verified = super.verifyDrawable(who); 8844 if (!verified && mDrawables != null) { 8845 for (Drawable dr : mDrawables.mShowing) { 8846 if (who == dr) { 8847 return true; 8848 } 8849 } 8850 } 8851 return verified; 8852 } 8853 8854 @Override 8855 public void jumpDrawablesToCurrentState() { 8856 super.jumpDrawablesToCurrentState(); 8857 if (mDrawables != null) { 8858 for (Drawable dr : mDrawables.mShowing) { 8859 if (dr != null) { 8860 dr.jumpToCurrentState(); 8861 } 8862 } 8863 } 8864 } 8865 8866 @Override 8867 public void invalidateDrawable(@NonNull Drawable drawable) { 8868 boolean handled = false; 8869 8870 if (verifyDrawable(drawable)) { 8871 final Rect dirty = drawable.getBounds(); 8872 int scrollX = mScrollX; 8873 int scrollY = mScrollY; 8874 8875 // IMPORTANT: The coordinates below are based on the coordinates computed 8876 // for each compound drawable in onDraw(). Make sure to update each section 8877 // accordingly. 8878 final TextView.Drawables drawables = mDrawables; 8879 if (drawables != null) { 8880 if (drawable == drawables.mShowing[Drawables.LEFT]) { 8881 final int compoundPaddingTop = getCompoundPaddingTop(); 8882 final int compoundPaddingBottom = getCompoundPaddingBottom(); 8883 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 8884 8885 scrollX += mPaddingLeft; 8886 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 8887 handled = true; 8888 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { 8889 final int compoundPaddingTop = getCompoundPaddingTop(); 8890 final int compoundPaddingBottom = getCompoundPaddingBottom(); 8891 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 8892 8893 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 8894 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 8895 handled = true; 8896 } else if (drawable == drawables.mShowing[Drawables.TOP]) { 8897 final int compoundPaddingLeft = getCompoundPaddingLeft(); 8898 final int compoundPaddingRight = getCompoundPaddingRight(); 8899 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 8900 8901 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 8902 scrollY += mPaddingTop; 8903 handled = true; 8904 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { 8905 final int compoundPaddingLeft = getCompoundPaddingLeft(); 8906 final int compoundPaddingRight = getCompoundPaddingRight(); 8907 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 8908 8909 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 8910 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 8911 handled = true; 8912 } 8913 } 8914 8915 if (handled) { 8916 invalidate(dirty.left + scrollX, dirty.top + scrollY, 8917 dirty.right + scrollX, dirty.bottom + scrollY); 8918 } 8919 } 8920 8921 if (!handled) { 8922 super.invalidateDrawable(drawable); 8923 } 8924 } 8925 8926 @Override 8927 public boolean hasOverlappingRendering() { 8928 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 8929 return ((getBackground() != null && getBackground().getCurrent() != null) 8930 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled() 8931 || mShadowColor != 0); 8932 } 8933 8934 /** 8935 * 8936 * Returns the state of the {@code textIsSelectable} flag (See 8937 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 8938 * to allow users to select and copy text in a non-editable TextView, the content of an 8939 * {@link EditText} can always be selected, independently of the value of this flag. 8940 * <p> 8941 * 8942 * @return True if the text displayed in this TextView can be selected by the user. 8943 * 8944 * @attr ref android.R.styleable#TextView_textIsSelectable 8945 */ 8946 @InspectableProperty(name = "textIsSelectable") 8947 public boolean isTextSelectable() { 8948 return mEditor == null ? false : mEditor.mTextIsSelectable; 8949 } 8950 8951 /** 8952 * Sets whether the content of this view is selectable by the user. The default is 8953 * {@code false}, meaning that the content is not selectable. 8954 * <p> 8955 * When you use a TextView to display a useful piece of information to the user (such as a 8956 * contact's address), make it selectable, so that the user can select and copy its 8957 * content. You can also use set the XML attribute 8958 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 8959 * <p> 8960 * When you call this method to set the value of {@code textIsSelectable}, it sets 8961 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 8962 * and {@code longClickable} to the same value. These flags correspond to the attributes 8963 * {@link android.R.styleable#View_focusable android:focusable}, 8964 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 8965 * {@link android.R.styleable#View_clickable android:clickable}, and 8966 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 8967 * flags to a state you had set previously, call one or more of the following methods: 8968 * {@link #setFocusable(boolean) setFocusable()}, 8969 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 8970 * {@link #setClickable(boolean) setClickable()} or 8971 * {@link #setLongClickable(boolean) setLongClickable()}. 8972 * 8973 * @param selectable Whether the content of this TextView should be selectable. 8974 */ 8975 public void setTextIsSelectable(boolean selectable) { 8976 if (!selectable && mEditor == null) return; // false is default value with no edit data 8977 8978 createEditorIfNeeded(); 8979 if (mEditor.mTextIsSelectable == selectable) return; 8980 8981 mEditor.mTextIsSelectable = selectable; 8982 setFocusableInTouchMode(selectable); 8983 setFocusable(FOCUSABLE_AUTO); 8984 setClickable(selectable); 8985 setLongClickable(selectable); 8986 8987 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 8988 8989 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 8990 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 8991 8992 // Called by setText above, but safer in case of future code changes 8993 mEditor.prepareCursorControllers(); 8994 } 8995 8996 @Override 8997 protected int[] onCreateDrawableState(int extraSpace) { 8998 final int[] drawableState; 8999 9000 if (mSingleLine) { 9001 drawableState = super.onCreateDrawableState(extraSpace); 9002 } else { 9003 drawableState = super.onCreateDrawableState(extraSpace + 1); 9004 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 9005 } 9006 9007 if (isTextSelectable()) { 9008 // Disable pressed state, which was introduced when TextView was made clickable. 9009 // Prevents text color change. 9010 // setClickable(false) would have a similar effect, but it also disables focus changes 9011 // and long press actions, which are both needed by text selection. 9012 final int length = drawableState.length; 9013 for (int i = 0; i < length; i++) { 9014 if (drawableState[i] == R.attr.state_pressed) { 9015 final int[] nonPressedState = new int[length - 1]; 9016 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 9017 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 9018 return nonPressedState; 9019 } 9020 } 9021 } 9022 9023 return drawableState; 9024 } 9025 9026 private void maybeUpdateHighlightPaths() { 9027 if (!mHighlightPathsBogus) { 9028 return; 9029 } 9030 9031 if (mHighlightPaths != null) { 9032 mPathRecyclePool.addAll(mHighlightPaths); 9033 mHighlightPaths.clear(); 9034 mHighlightPaints.clear(); 9035 } else { 9036 mHighlightPaths = new ArrayList<>(); 9037 mHighlightPaints = new ArrayList<>(); 9038 } 9039 9040 if (mHighlights != null) { 9041 for (int i = 0; i < mHighlights.getSize(); ++i) { 9042 final int[] ranges = mHighlights.getRanges(i); 9043 final Paint paint = mHighlights.getPaint(i); 9044 final Path path; 9045 if (mPathRecyclePool.isEmpty()) { 9046 path = new Path(); 9047 } else { 9048 path = mPathRecyclePool.get(mPathRecyclePool.size() - 1); 9049 mPathRecyclePool.remove(mPathRecyclePool.size() - 1); 9050 path.reset(); 9051 } 9052 9053 boolean atLeastOnePathAdded = false; 9054 for (int j = 0; j < ranges.length / 2; ++j) { 9055 final int start = ranges[2 * j]; 9056 final int end = ranges[2 * j + 1]; 9057 if (start < end) { 9058 mLayout.getSelection(start, end, (left, top, right, bottom, layout) -> 9059 path.addRect(left, top, right, bottom, Path.Direction.CW) 9060 ); 9061 atLeastOnePathAdded = true; 9062 } 9063 } 9064 if (atLeastOnePathAdded) { 9065 mHighlightPaths.add(path); 9066 mHighlightPaints.add(paint); 9067 } 9068 } 9069 } 9070 9071 addSearchHighlightPaths(); 9072 9073 if (hasGesturePreviewHighlight()) { 9074 final Path path; 9075 if (mPathRecyclePool.isEmpty()) { 9076 path = new Path(); 9077 } else { 9078 path = mPathRecyclePool.get(mPathRecyclePool.size() - 1); 9079 mPathRecyclePool.remove(mPathRecyclePool.size() - 1); 9080 path.reset(); 9081 } 9082 mLayout.getSelectionPath( 9083 mGesturePreviewHighlightStart, mGesturePreviewHighlightEnd, path); 9084 mHighlightPaths.add(path); 9085 mHighlightPaints.add(mGesturePreviewHighlightPaint); 9086 } 9087 9088 mHighlightPathsBogus = false; 9089 } 9090 9091 private void addSearchHighlightPaths() { 9092 if (mSearchResultHighlights != null) { 9093 final Path searchResultPath; 9094 if (mPathRecyclePool.isEmpty()) { 9095 searchResultPath = new Path(); 9096 } else { 9097 searchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1); 9098 mPathRecyclePool.remove(mPathRecyclePool.size() - 1); 9099 searchResultPath.reset(); 9100 } 9101 final Path focusedSearchResultPath; 9102 if (mFocusedSearchResultIndex == FOCUSED_SEARCH_RESULT_INDEX_NONE) { 9103 focusedSearchResultPath = null; 9104 } else if (mPathRecyclePool.isEmpty()) { 9105 focusedSearchResultPath = new Path(); 9106 } else { 9107 focusedSearchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1); 9108 mPathRecyclePool.remove(mPathRecyclePool.size() - 1); 9109 focusedSearchResultPath.reset(); 9110 } 9111 9112 boolean atLeastOnePathAdded = false; 9113 for (int j = 0; j < mSearchResultHighlights.length / 2; ++j) { 9114 final int start = mSearchResultHighlights[2 * j]; 9115 final int end = mSearchResultHighlights[2 * j + 1]; 9116 if (start < end) { 9117 if (j == mFocusedSearchResultIndex) { 9118 mLayout.getSelection(start, end, (left, top, right, bottom, layout) -> 9119 focusedSearchResultPath.addRect(left, top, right, bottom, 9120 Path.Direction.CW) 9121 ); 9122 } else { 9123 mLayout.getSelection(start, end, (left, top, right, bottom, layout) -> 9124 searchResultPath.addRect(left, top, right, bottom, 9125 Path.Direction.CW) 9126 ); 9127 atLeastOnePathAdded = true; 9128 } 9129 } 9130 } 9131 if (atLeastOnePathAdded) { 9132 if (mSearchResultHighlightPaint == null) { 9133 mSearchResultHighlightPaint = new Paint(); 9134 } 9135 mSearchResultHighlightPaint.setColor(mSearchResultHighlightColor); 9136 mSearchResultHighlightPaint.setStyle(Paint.Style.FILL); 9137 mHighlightPaths.add(searchResultPath); 9138 mHighlightPaints.add(mSearchResultHighlightPaint); 9139 } 9140 if (focusedSearchResultPath != null) { 9141 if (mFocusedSearchResultHighlightPaint == null) { 9142 mFocusedSearchResultHighlightPaint = new Paint(); 9143 } 9144 mFocusedSearchResultHighlightPaint.setColor(mFocusedSearchResultHighlightColor); 9145 mFocusedSearchResultHighlightPaint.setStyle(Paint.Style.FILL); 9146 mHighlightPaths.add(focusedSearchResultPath); 9147 mHighlightPaints.add(mFocusedSearchResultHighlightPaint); 9148 } 9149 } 9150 } 9151 9152 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 9153 private Path getUpdatedHighlightPath() { 9154 Path highlight = null; 9155 Paint highlightPaint = mHighlightPaint; 9156 9157 final int selStart = getSelectionStartTransformed(); 9158 final int selEnd = getSelectionEndTransformed(); 9159 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 9160 if (selStart == selEnd) { 9161 if (mEditor != null && mEditor.shouldRenderCursor()) { 9162 if (mHighlightPathBogus) { 9163 if (mHighlightPath == null) mHighlightPath = new Path(); 9164 mHighlightPath.reset(); 9165 mLayout.getCursorPath(selStart, mHighlightPath, mText); 9166 mEditor.updateCursorPosition(); 9167 mHighlightPathBogus = false; 9168 } 9169 9170 // XXX should pass to skin instead of drawing directly 9171 highlightPaint.setColor(mCurTextColor); 9172 highlightPaint.setStyle(Paint.Style.STROKE); 9173 highlight = mHighlightPath; 9174 } 9175 } else { 9176 if (mHighlightPathBogus) { 9177 if (mHighlightPath == null) mHighlightPath = new Path(); 9178 mHighlightPath.reset(); 9179 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 9180 mHighlightPathBogus = false; 9181 } 9182 9183 // XXX should pass to skin instead of drawing directly 9184 highlightPaint.setColor(mHighlightColor); 9185 highlightPaint.setStyle(Paint.Style.FILL); 9186 9187 highlight = mHighlightPath; 9188 } 9189 } 9190 return highlight; 9191 } 9192 9193 /** 9194 * @hide 9195 */ 9196 public int getHorizontalOffsetForDrawables() { 9197 return 0; 9198 } 9199 9200 @Override 9201 protected void onDraw(Canvas canvas) { 9202 restartMarqueeIfNeeded(); 9203 9204 // Draw the background for this view 9205 super.onDraw(canvas); 9206 9207 final int compoundPaddingLeft = getCompoundPaddingLeft(); 9208 final int compoundPaddingTop = getCompoundPaddingTop(); 9209 final int compoundPaddingRight = getCompoundPaddingRight(); 9210 final int compoundPaddingBottom = getCompoundPaddingBottom(); 9211 final int scrollX = mScrollX; 9212 final int scrollY = mScrollY; 9213 final int right = mRight; 9214 final int left = mLeft; 9215 final int bottom = mBottom; 9216 final int top = mTop; 9217 final boolean isLayoutRtl = isLayoutRtl(); 9218 final int offset = getHorizontalOffsetForDrawables(); 9219 final int leftOffset = isLayoutRtl ? 0 : offset; 9220 final int rightOffset = isLayoutRtl ? offset : 0; 9221 9222 final Drawables dr = mDrawables; 9223 if (dr != null) { 9224 /* 9225 * Compound, not extended, because the icon is not clipped 9226 * if the text height is smaller. 9227 */ 9228 9229 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 9230 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 9231 9232 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 9233 // Make sure to update invalidateDrawable() when changing this code. 9234 if (dr.mShowing[Drawables.LEFT] != null) { 9235 canvas.save(); 9236 canvas.translate(scrollX + mPaddingLeft + leftOffset, 9237 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); 9238 dr.mShowing[Drawables.LEFT].draw(canvas); 9239 canvas.restore(); 9240 } 9241 9242 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 9243 // Make sure to update invalidateDrawable() when changing this code. 9244 if (dr.mShowing[Drawables.RIGHT] != null) { 9245 canvas.save(); 9246 canvas.translate(scrollX + right - left - mPaddingRight 9247 - dr.mDrawableSizeRight - rightOffset, 9248 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 9249 dr.mShowing[Drawables.RIGHT].draw(canvas); 9250 canvas.restore(); 9251 } 9252 9253 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 9254 // Make sure to update invalidateDrawable() when changing this code. 9255 if (dr.mShowing[Drawables.TOP] != null) { 9256 canvas.save(); 9257 canvas.translate(scrollX + compoundPaddingLeft 9258 + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 9259 dr.mShowing[Drawables.TOP].draw(canvas); 9260 canvas.restore(); 9261 } 9262 9263 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 9264 // Make sure to update invalidateDrawable() when changing this code. 9265 if (dr.mShowing[Drawables.BOTTOM] != null) { 9266 canvas.save(); 9267 canvas.translate(scrollX + compoundPaddingLeft 9268 + (hspace - dr.mDrawableWidthBottom) / 2, 9269 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 9270 dr.mShowing[Drawables.BOTTOM].draw(canvas); 9271 canvas.restore(); 9272 } 9273 } 9274 9275 int color = mCurTextColor; 9276 9277 if (mLayout == null) { 9278 assumeLayout(); 9279 } 9280 9281 Layout layout = mLayout; 9282 9283 if (mHint != null && !mHideHint && mText.length() == 0) { 9284 if (mHintTextColor != null) { 9285 color = mCurHintTextColor; 9286 } 9287 9288 layout = mHintLayout; 9289 } 9290 9291 mTextPaint.setColor(color); 9292 mTextPaint.drawableState = getDrawableState(); 9293 9294 canvas.save(); 9295 /* Would be faster if we didn't have to do this. Can we chop the 9296 (displayable) text so that we don't need to do this ever? 9297 */ 9298 9299 int extendedPaddingTop = getExtendedPaddingTop(); 9300 int extendedPaddingBottom = getExtendedPaddingBottom(); 9301 9302 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 9303 final int maxScrollY = mLayout.getHeight() - vspace; 9304 9305 float clipLeft = compoundPaddingLeft + scrollX; 9306 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 9307 float clipRight = right - left - getCompoundPaddingRight() + scrollX; 9308 float clipBottom = bottom - top + scrollY 9309 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 9310 9311 if (mShadowRadius != 0) { 9312 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 9313 clipRight += Math.max(0, mShadowDx + mShadowRadius); 9314 9315 clipTop += Math.min(0, mShadowDy - mShadowRadius); 9316 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 9317 } 9318 9319 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 9320 9321 int voffsetText = 0; 9322 int voffsetCursor = 0; 9323 9324 // translate in by our padding 9325 /* shortcircuit calling getVerticaOffset() */ 9326 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9327 voffsetText = getVerticalOffset(false); 9328 voffsetCursor = getVerticalOffset(true); 9329 } 9330 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 9331 9332 final int layoutDirection = getLayoutDirection(); 9333 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 9334 if (isMarqueeFadeEnabled()) { 9335 if (!mSingleLine && getLineCount() == 1 && canMarquee() 9336 && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 9337 final int width = mRight - mLeft; 9338 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 9339 final float dx = mLayout.getLineRight(0) - (width - padding); 9340 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 9341 } 9342 9343 if (mMarquee != null && mMarquee.isRunning()) { 9344 final float dx = -mMarquee.getScroll(); 9345 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 9346 } 9347 } 9348 9349 final int cursorOffsetVertical = voffsetCursor - voffsetText; 9350 9351 maybeUpdateHighlightPaths(); 9352 // If there is a gesture preview highlight, then the selection or cursor is not drawn. 9353 Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath(); 9354 if (mEditor != null) { 9355 mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight, 9356 mHighlightPaint, cursorOffsetVertical); 9357 } else { 9358 layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint, 9359 cursorOffsetVertical); 9360 } 9361 9362 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 9363 final float dx = mMarquee.getGhostOffset(); 9364 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 9365 layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint, 9366 cursorOffsetVertical); 9367 } 9368 9369 canvas.restore(); 9370 } 9371 9372 @Override 9373 public void getFocusedRect(Rect r) { 9374 if (mLayout == null) { 9375 super.getFocusedRect(r); 9376 return; 9377 } 9378 9379 int selEnd = getSelectionEndTransformed(); 9380 if (selEnd < 0) { 9381 super.getFocusedRect(r); 9382 return; 9383 } 9384 9385 int selStart = getSelectionStartTransformed(); 9386 if (selStart < 0 || selStart >= selEnd) { 9387 int line = mLayout.getLineForOffset(selEnd); 9388 r.top = mLayout.getLineTop(line); 9389 r.bottom = mLayout.getLineBottom(line); 9390 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 9391 r.right = r.left + 4; 9392 } else { 9393 int lineStart = mLayout.getLineForOffset(selStart); 9394 int lineEnd = mLayout.getLineForOffset(selEnd); 9395 r.top = mLayout.getLineTop(lineStart); 9396 r.bottom = mLayout.getLineBottom(lineEnd); 9397 if (lineStart == lineEnd) { 9398 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 9399 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 9400 } else { 9401 // Selection extends across multiple lines -- make the focused 9402 // rect cover the entire width. 9403 if (mHighlightPathBogus) { 9404 if (mHighlightPath == null) mHighlightPath = new Path(); 9405 mHighlightPath.reset(); 9406 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 9407 mHighlightPathBogus = false; 9408 } 9409 synchronized (TEMP_RECTF) { 9410 mHighlightPath.computeBounds(TEMP_RECTF, true); 9411 r.left = (int) TEMP_RECTF.left - 1; 9412 r.right = (int) TEMP_RECTF.right + 1; 9413 } 9414 } 9415 } 9416 9417 // Adjust for padding and gravity. 9418 int paddingLeft = getCompoundPaddingLeft(); 9419 int paddingTop = getExtendedPaddingTop(); 9420 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9421 paddingTop += getVerticalOffset(false); 9422 } 9423 r.offset(paddingLeft, paddingTop); 9424 int paddingBottom = getExtendedPaddingBottom(); 9425 r.bottom += paddingBottom; 9426 } 9427 9428 /** 9429 * Return the number of lines of text, or 0 if the internal Layout has not 9430 * been built. 9431 */ 9432 public int getLineCount() { 9433 return mLayout != null ? mLayout.getLineCount() : 0; 9434 } 9435 9436 /** 9437 * Return the baseline for the specified line (0...getLineCount() - 1) 9438 * If bounds is not null, return the top, left, right, bottom extents 9439 * of the specified line in it. If the internal Layout has not been built, 9440 * return 0 and set bounds to (0, 0, 0, 0) 9441 * @param line which line to examine (0..getLineCount() - 1) 9442 * @param bounds Optional. If not null, it returns the extent of the line 9443 * @return the Y-coordinate of the baseline 9444 */ 9445 public int getLineBounds(int line, Rect bounds) { 9446 if (mLayout == null) { 9447 if (bounds != null) { 9448 bounds.set(0, 0, 0, 0); 9449 } 9450 return 0; 9451 } else { 9452 int baseline = mLayout.getLineBounds(line, bounds); 9453 9454 int voffset = getExtendedPaddingTop(); 9455 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9456 voffset += getVerticalOffset(true); 9457 } 9458 if (bounds != null) { 9459 bounds.offset(getCompoundPaddingLeft(), voffset); 9460 } 9461 return baseline + voffset; 9462 } 9463 } 9464 9465 @Override 9466 public int getBaseline() { 9467 if (mLayout == null) { 9468 return super.getBaseline(); 9469 } 9470 9471 return getBaselineOffset() + mLayout.getLineBaseline(0); 9472 } 9473 9474 int getBaselineOffset() { 9475 int voffset = 0; 9476 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9477 voffset = getVerticalOffset(true); 9478 } 9479 9480 if (isLayoutModeOptical(mParent)) { 9481 voffset -= getOpticalInsets().top; 9482 } 9483 9484 return getExtendedPaddingTop() + voffset; 9485 } 9486 9487 /** 9488 * @hide 9489 */ 9490 @Override 9491 protected int getFadeTop(boolean offsetRequired) { 9492 if (mLayout == null) return 0; 9493 9494 int voffset = 0; 9495 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9496 voffset = getVerticalOffset(true); 9497 } 9498 9499 if (offsetRequired) voffset += getTopPaddingOffset(); 9500 9501 return getExtendedPaddingTop() + voffset; 9502 } 9503 9504 /** 9505 * @hide 9506 */ 9507 @Override 9508 protected int getFadeHeight(boolean offsetRequired) { 9509 return mLayout != null ? mLayout.getHeight() : 0; 9510 } 9511 9512 @Override 9513 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 9514 if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { 9515 if (mSpannable != null && mLinksClickable) { 9516 final float x = event.getX(pointerIndex); 9517 final float y = event.getY(pointerIndex); 9518 final int offset = getOffsetForPosition(x, y); 9519 final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset, 9520 ClickableSpan.class); 9521 if (clickables.length > 0) { 9522 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND); 9523 } 9524 } 9525 if (isTextSelectable() || isTextEditable()) { 9526 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT); 9527 } 9528 } 9529 return super.onResolvePointerIcon(event, pointerIndex); 9530 } 9531 9532 @Override 9533 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 9534 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, 9535 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call 9536 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event). 9537 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) { 9538 return true; 9539 } 9540 return super.onKeyPreIme(keyCode, event); 9541 } 9542 9543 /** 9544 * @hide 9545 */ 9546 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) { 9547 // Do nothing unless mEditor is in text action mode. 9548 if (mEditor == null || mEditor.getTextActionMode() == null) { 9549 return false; 9550 } 9551 9552 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 9553 KeyEvent.DispatcherState state = getKeyDispatcherState(); 9554 if (state != null) { 9555 state.startTracking(event, this); 9556 } 9557 return true; 9558 } else if (event.getAction() == KeyEvent.ACTION_UP) { 9559 KeyEvent.DispatcherState state = getKeyDispatcherState(); 9560 if (state != null) { 9561 state.handleUpEvent(event); 9562 } 9563 if (event.isTracking() && !event.isCanceled()) { 9564 stopTextActionMode(); 9565 return true; 9566 } 9567 } 9568 return false; 9569 } 9570 9571 @Override 9572 public boolean onKeyDown(int keyCode, KeyEvent event) { 9573 final int which = doKeyDown(keyCode, event, null); 9574 if (which == KEY_EVENT_NOT_HANDLED) { 9575 return super.onKeyDown(keyCode, event); 9576 } 9577 9578 return true; 9579 } 9580 9581 @Override 9582 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 9583 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 9584 final int which = doKeyDown(keyCode, down, event); 9585 if (which == KEY_EVENT_NOT_HANDLED) { 9586 // Go through default dispatching. 9587 return super.onKeyMultiple(keyCode, repeatCount, event); 9588 } 9589 if (which == KEY_EVENT_HANDLED) { 9590 // Consumed the whole thing. 9591 return true; 9592 } 9593 9594 repeatCount--; 9595 9596 // We are going to dispatch the remaining events to either the input 9597 // or movement method. To do this, we will just send a repeated stream 9598 // of down and up events until we have done the complete repeatCount. 9599 // It would be nice if those interfaces had an onKeyMultiple() method, 9600 // but adding that is a more complicated change. 9601 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 9602 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) { 9603 // mEditor and mEditor.mInput are not null from doKeyDown 9604 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 9605 while (--repeatCount > 0) { 9606 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down); 9607 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 9608 } 9609 hideErrorIfUnchanged(); 9610 9611 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) { 9612 // mMovement is not null from doKeyDown 9613 mMovement.onKeyUp(this, mSpannable, keyCode, up); 9614 while (--repeatCount > 0) { 9615 mMovement.onKeyDown(this, mSpannable, keyCode, down); 9616 mMovement.onKeyUp(this, mSpannable, keyCode, up); 9617 } 9618 } 9619 9620 return true; 9621 } 9622 9623 /** 9624 * Returns true if pressing ENTER in this field advances focus instead 9625 * of inserting the character. This is true mostly in single-line fields, 9626 * but also in mail addresses and subjects which will display on multiple 9627 * lines but where it doesn't make sense to insert newlines. 9628 */ 9629 private boolean shouldAdvanceFocusOnEnter() { 9630 if (getKeyListener() == null) { 9631 return false; 9632 } 9633 9634 if (mSingleLine) { 9635 return true; 9636 } 9637 9638 if (mEditor != null 9639 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 9640 == EditorInfo.TYPE_CLASS_TEXT) { 9641 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 9642 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 9643 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 9644 return true; 9645 } 9646 } 9647 9648 return false; 9649 } 9650 9651 private boolean isDirectionalNavigationKey(int keyCode) { 9652 switch(keyCode) { 9653 case KeyEvent.KEYCODE_DPAD_UP: 9654 case KeyEvent.KEYCODE_DPAD_DOWN: 9655 case KeyEvent.KEYCODE_DPAD_LEFT: 9656 case KeyEvent.KEYCODE_DPAD_RIGHT: 9657 return true; 9658 } 9659 return false; 9660 } 9661 9662 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 9663 if (!isEnabled()) { 9664 return KEY_EVENT_NOT_HANDLED; 9665 } 9666 9667 // If this is the initial keydown, we don't want to prevent a movement away from this view. 9668 // While this shouldn't be necessary because any time we're preventing default movement we 9669 // should be restricting the focus to remain within this view, thus we'll also receive 9670 // the key up event, occasionally key up events will get dropped and we don't want to 9671 // prevent the user from traversing out of this on the next key down. 9672 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 9673 mPreventDefaultMovement = false; 9674 } 9675 9676 switch (keyCode) { 9677 case KeyEvent.KEYCODE_ENTER: 9678 case KeyEvent.KEYCODE_NUMPAD_ENTER: 9679 if (event.hasNoModifiers()) { 9680 // When mInputContentType is set, we know that we are 9681 // running in a "modern" cupcake environment, so don't need 9682 // to worry about the application trying to capture 9683 // enter key events. 9684 if (mEditor != null && mEditor.mInputContentType != null) { 9685 // If there is an action listener, given them a 9686 // chance to consume the event. 9687 if (mEditor.mInputContentType.onEditorActionListener != null 9688 && mEditor.mInputContentType.onEditorActionListener.onEditorAction( 9689 this, 9690 getActionIdForEnterEvent(), 9691 event)) { 9692 mEditor.mInputContentType.enterDown = true; 9693 // We are consuming the enter key for them. 9694 return KEY_EVENT_HANDLED; 9695 } 9696 } 9697 9698 // If our editor should move focus when enter is pressed, or 9699 // this is a generated event from an IME action button, then 9700 // don't let it be inserted into the text. 9701 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 9702 || shouldAdvanceFocusOnEnter()) { 9703 if (hasOnClickListeners()) { 9704 return KEY_EVENT_NOT_HANDLED; 9705 } 9706 return KEY_EVENT_HANDLED; 9707 } 9708 } 9709 break; 9710 9711 case KeyEvent.KEYCODE_DPAD_CENTER: 9712 if (event.hasNoModifiers()) { 9713 if (shouldAdvanceFocusOnEnter()) { 9714 return KEY_EVENT_NOT_HANDLED; 9715 } 9716 } 9717 break; 9718 9719 case KeyEvent.KEYCODE_TAB: 9720 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 9721 // Tab is used to move focus. 9722 return KEY_EVENT_NOT_HANDLED; 9723 } 9724 break; 9725 9726 // Has to be done on key down (and not on key up) to correctly be intercepted. 9727 case KeyEvent.KEYCODE_BACK: 9728 if (mEditor != null && mEditor.getTextActionMode() != null) { 9729 stopTextActionMode(); 9730 return KEY_EVENT_HANDLED; 9731 } 9732 break; 9733 9734 case KeyEvent.KEYCODE_ESCAPE: 9735 if (com.android.text.flags.Flags.escapeClearsFocus() && event.hasNoModifiers()) { 9736 if (mEditor != null && mEditor.getTextActionMode() != null) { 9737 stopTextActionMode(); 9738 return KEY_EVENT_HANDLED; 9739 } 9740 if (hasFocus()) { 9741 clearFocusInternal(null, /* propagate */ true, /* refocus */ false); 9742 InputMethodManager imm = getInputMethodManager(); 9743 if (imm != null) { 9744 imm.hideSoftInputFromView(this, 0); 9745 } 9746 return KEY_EVENT_HANDLED; 9747 } 9748 } 9749 break; 9750 9751 case KeyEvent.KEYCODE_CUT: 9752 if (event.hasNoModifiers() && canCut()) { 9753 if (onTextContextMenuItem(ID_CUT)) { 9754 return KEY_EVENT_HANDLED; 9755 } 9756 } 9757 break; 9758 9759 case KeyEvent.KEYCODE_COPY: 9760 if (event.hasNoModifiers() && canCopy()) { 9761 if (onTextContextMenuItem(ID_COPY)) { 9762 return KEY_EVENT_HANDLED; 9763 } 9764 } 9765 break; 9766 9767 case KeyEvent.KEYCODE_PASTE: 9768 if (event.hasNoModifiers() && canPaste()) { 9769 if (onTextContextMenuItem(ID_PASTE)) { 9770 return KEY_EVENT_HANDLED; 9771 } 9772 } 9773 break; 9774 9775 case KeyEvent.KEYCODE_FORWARD_DEL: 9776 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) { 9777 if (onTextContextMenuItem(ID_CUT)) { 9778 return KEY_EVENT_HANDLED; 9779 } 9780 } 9781 break; 9782 9783 case KeyEvent.KEYCODE_INSERT: 9784 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) { 9785 if (onTextContextMenuItem(ID_COPY)) { 9786 return KEY_EVENT_HANDLED; 9787 } 9788 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) { 9789 if (onTextContextMenuItem(ID_PASTE)) { 9790 return KEY_EVENT_HANDLED; 9791 } 9792 } 9793 break; 9794 } 9795 9796 if (mEditor != null && mEditor.mKeyListener != null) { 9797 boolean doDown = true; 9798 if (otherEvent != null) { 9799 try { 9800 beginBatchEdit(); 9801 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 9802 otherEvent); 9803 hideErrorIfUnchanged(); 9804 doDown = false; 9805 if (handled) { 9806 return KEY_EVENT_HANDLED; 9807 } 9808 } catch (AbstractMethodError e) { 9809 // onKeyOther was added after 1.0, so if it isn't 9810 // implemented we need to try to dispatch as a regular down. 9811 } finally { 9812 endBatchEdit(); 9813 } 9814 } 9815 9816 if (doDown) { 9817 beginBatchEdit(); 9818 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 9819 keyCode, event); 9820 endBatchEdit(); 9821 hideErrorIfUnchanged(); 9822 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER; 9823 } 9824 } 9825 9826 // bug 650865: sometimes we get a key event before a layout. 9827 // don't try to move around if we don't know the layout. 9828 9829 if (mMovement != null && mLayout != null) { 9830 boolean doDown = true; 9831 if (otherEvent != null) { 9832 try { 9833 boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent); 9834 doDown = false; 9835 if (handled) { 9836 return KEY_EVENT_HANDLED; 9837 } 9838 } catch (AbstractMethodError e) { 9839 // onKeyOther was added after 1.0, so if it isn't 9840 // implemented we need to try to dispatch as a regular down. 9841 } 9842 } 9843 if (doDown) { 9844 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) { 9845 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 9846 mPreventDefaultMovement = true; 9847 } 9848 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD; 9849 } 9850 } 9851 // Consume arrows from keyboard devices to prevent focus leaving the editor. 9852 // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those 9853 // to move focus with arrows. 9854 if (event.getSource() == InputDevice.SOURCE_KEYBOARD 9855 && isDirectionalNavigationKey(keyCode)) { 9856 return KEY_EVENT_HANDLED; 9857 } 9858 } 9859 9860 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) 9861 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED; 9862 } 9863 9864 /** 9865 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 9866 * can be recorded. 9867 * @hide 9868 */ 9869 public void resetErrorChangedFlag() { 9870 /* 9871 * Keep track of what the error was before doing the input 9872 * so that if an input filter changed the error, we leave 9873 * that error showing. Otherwise, we take down whatever 9874 * error was showing when the user types something. 9875 */ 9876 if (mEditor != null) mEditor.mErrorWasChanged = false; 9877 } 9878 9879 /** 9880 * @hide 9881 */ 9882 public void hideErrorIfUnchanged() { 9883 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 9884 setError(null, null); 9885 } 9886 } 9887 9888 @Override 9889 public boolean onKeyUp(int keyCode, KeyEvent event) { 9890 if (!isEnabled()) { 9891 return super.onKeyUp(keyCode, event); 9892 } 9893 9894 if (!KeyEvent.isModifierKey(keyCode)) { 9895 mPreventDefaultMovement = false; 9896 } 9897 9898 switch (keyCode) { 9899 case KeyEvent.KEYCODE_DPAD_CENTER: 9900 if (event.hasNoModifiers()) { 9901 /* 9902 * If there is a click listener, just call through to 9903 * super, which will invoke it. 9904 * 9905 * If there isn't a click listener, try to show the soft 9906 * input method. (It will also 9907 * call performClick(), but that won't do anything in 9908 * this case.) 9909 */ 9910 if (!hasOnClickListeners()) { 9911 if (mMovement != null && mText instanceof Editable 9912 && mLayout != null && onCheckIsTextEditor()) { 9913 InputMethodManager imm = getInputMethodManager(); 9914 viewClicked(imm); 9915 if (imm != null && getShowSoftInputOnFocus()) { 9916 imm.showSoftInput(this, 0); 9917 } 9918 } 9919 } 9920 } 9921 return super.onKeyUp(keyCode, event); 9922 9923 case KeyEvent.KEYCODE_ENTER: 9924 case KeyEvent.KEYCODE_NUMPAD_ENTER: 9925 if (event.hasNoModifiers()) { 9926 if (mEditor != null && mEditor.mInputContentType != null 9927 && mEditor.mInputContentType.onEditorActionListener != null 9928 && mEditor.mInputContentType.enterDown) { 9929 mEditor.mInputContentType.enterDown = false; 9930 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 9931 this, getActionIdForEnterEvent(), event)) { 9932 return true; 9933 } 9934 } 9935 9936 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 9937 || shouldAdvanceFocusOnEnter()) { 9938 /* 9939 * If there is a click listener, just call through to 9940 * super, which will invoke it. 9941 * 9942 * If there isn't a click listener, try to advance focus, 9943 * but still call through to super, which will reset the 9944 * pressed state and longpress state. (It will also 9945 * call performClick(), but that won't do anything in 9946 * this case.) 9947 */ 9948 if (!hasOnClickListeners()) { 9949 View v = focusSearch(FOCUS_DOWN); 9950 9951 if (v != null) { 9952 if (!v.requestFocus(FOCUS_DOWN)) { 9953 throw new IllegalStateException("focus search returned a view " 9954 + "that wasn't able to take focus!"); 9955 } 9956 9957 /* 9958 * Return true because we handled the key; super 9959 * will return false because there was no click 9960 * listener. 9961 */ 9962 super.onKeyUp(keyCode, event); 9963 return true; 9964 } else if ((event.getFlags() 9965 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 9966 // No target for next focus, but make sure the IME 9967 // if this came from it. 9968 InputMethodManager imm = getInputMethodManager(); 9969 if (imm != null) { 9970 imm.hideSoftInputFromView(this, 0); 9971 } 9972 } 9973 } 9974 } 9975 return super.onKeyUp(keyCode, event); 9976 } 9977 break; 9978 } 9979 9980 if (mEditor != null && mEditor.mKeyListener != null) { 9981 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) { 9982 return true; 9983 } 9984 } 9985 9986 if (mMovement != null && mLayout != null) { 9987 if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) { 9988 return true; 9989 } 9990 } 9991 9992 return super.onKeyUp(keyCode, event); 9993 } 9994 9995 private int getActionIdForEnterEvent() { 9996 // If it's not single line, no action 9997 if (!isSingleLine()) { 9998 return EditorInfo.IME_NULL; 9999 } 10000 // Return the action that was specified for Enter 10001 return getImeOptions() & EditorInfo.IME_MASK_ACTION; 10002 } 10003 10004 @Override 10005 public boolean onCheckIsTextEditor() { 10006 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 10007 } 10008 10009 private boolean hasEditorInFocusSearchDirection(@FocusRealDirection int direction) { 10010 final View nextView = focusSearch(direction); 10011 return nextView != null && nextView.onCheckIsTextEditor(); 10012 } 10013 10014 @Override 10015 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 10016 if (onCheckIsTextEditor() && isEnabled()) { 10017 mEditor.createInputMethodStateIfNeeded(); 10018 mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = 0; 10019 mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = 0; 10020 10021 outAttrs.inputType = getInputType(); 10022 if (mEditor.mInputContentType != null) { 10023 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 10024 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 10025 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 10026 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 10027 outAttrs.extras = mEditor.mInputContentType.extras; 10028 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales; 10029 } else { 10030 outAttrs.imeOptions = EditorInfo.IME_NULL; 10031 outAttrs.hintLocales = null; 10032 } 10033 if (hasEditorInFocusSearchDirection(FOCUS_DOWN)) { 10034 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 10035 } 10036 if (hasEditorInFocusSearchDirection(FOCUS_UP)) { 10037 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 10038 } 10039 if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION) 10040 == EditorInfo.IME_ACTION_UNSPECIFIED) { 10041 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 10042 // An action has not been set, but the enter key will move to 10043 // the next focus, so set the action to that. 10044 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 10045 } else { 10046 // An action has not been set, and there is no focus to move 10047 // to, so let's just supply a "done" action. 10048 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 10049 } 10050 if (!shouldAdvanceFocusOnEnter()) { 10051 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 10052 } 10053 } 10054 if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) { 10055 outAttrs.internalImeOptions |= EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT; 10056 } 10057 if (isMultilineInputType(outAttrs.inputType)) { 10058 // Multi-line text editors should always show an enter key. 10059 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 10060 } 10061 outAttrs.hintText = mHint; 10062 outAttrs.targetInputMethodUser = mTextOperationUser; 10063 if (mText instanceof Editable) { 10064 InputConnection ic = new EditableInputConnection(this); 10065 outAttrs.initialSelStart = getSelectionStart(); 10066 outAttrs.initialSelEnd = getSelectionEnd(); 10067 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 10068 outAttrs.setInitialSurroundingText(mText); 10069 outAttrs.contentMimeTypes = getReceiveContentMimeTypes(); 10070 if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()) { 10071 boolean handwritingEnabled = isAutoHandwritingEnabled(); 10072 outAttrs.setStylusHandwritingEnabled(handwritingEnabled); 10073 // AndroidX Core library 1.13.0 introduced 10074 // EditorInfoCompat#setStylusHandwritingEnabled and 10075 // EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the 10076 // EditorInfo extras bundle. These methods do not set or check the Android V 10077 // property since the Android V SDK was not yet available. In order for 10078 // EditorInfoCompat#isStylusHandwritingEnabled to return the correct value for 10079 // EditorInfo created by Android V TextView, the extras bundle value is also set 10080 // here. 10081 if (outAttrs.extras == null) { 10082 outAttrs.extras = new Bundle(); 10083 } 10084 outAttrs.extras.putBoolean( 10085 STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY, handwritingEnabled); 10086 } 10087 ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>(); 10088 gestures.add(SelectGesture.class); 10089 gestures.add(SelectRangeGesture.class); 10090 gestures.add(DeleteGesture.class); 10091 gestures.add(DeleteRangeGesture.class); 10092 gestures.add(InsertGesture.class); 10093 gestures.add(RemoveSpaceGesture.class); 10094 gestures.add(JoinOrSplitGesture.class); 10095 gestures.add(InsertModeGesture.class); 10096 outAttrs.setSupportedHandwritingGestures(gestures); 10097 10098 Set<Class<? extends PreviewableHandwritingGesture>> previews = new ArraySet<>(); 10099 previews.add(SelectGesture.class); 10100 previews.add(SelectRangeGesture.class); 10101 previews.add(DeleteGesture.class); 10102 previews.add(DeleteRangeGesture.class); 10103 outAttrs.setSupportedHandwritingGesturePreviews(previews); 10104 10105 return ic; 10106 } 10107 } 10108 return null; 10109 } 10110 10111 /** 10112 * Called back by the system to handle {@link InputConnection#requestCursorUpdates(int, int)}. 10113 * 10114 * @param cursorUpdateMode modes defined in {@link InputConnection.CursorUpdateMode}. 10115 * @param cursorUpdateFilter modes defined in {@link InputConnection.CursorUpdateFilter}. 10116 * 10117 * @hide 10118 */ 10119 public void onRequestCursorUpdatesInternal( 10120 @InputConnection.CursorUpdateMode int cursorUpdateMode, 10121 @InputConnection.CursorUpdateFilter int cursorUpdateFilter) { 10122 mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = cursorUpdateMode; 10123 mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = cursorUpdateFilter; 10124 if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) == 0) { 10125 return; 10126 } 10127 if (isInLayout()) { 10128 // In this case, the view hierarchy is currently undergoing a layout pass. 10129 // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout 10130 // pass is finished. 10131 } else { 10132 // This will schedule a layout pass of the view tree, and the layout event 10133 // eventually triggers IMM#updateCursorAnchorInfo. 10134 requestLayout(); 10135 } 10136 } 10137 10138 /** 10139 * If this TextView contains editable content, extract a portion of it 10140 * based on the information in <var>request</var> in to <var>outText</var>. 10141 * @return Returns true if the text was successfully extracted, else false. 10142 */ 10143 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 10144 createEditorIfNeeded(); 10145 return mEditor.extractText(request, outText); 10146 } 10147 10148 /** 10149 * This is used to remove all style-impacting spans from text before new 10150 * extracted text is being replaced into it, so that we don't have any 10151 * lingering spans applied during the replace. 10152 */ 10153 static void removeParcelableSpans(Spannable spannable, int start, int end) { 10154 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 10155 int i = spans.length; 10156 while (i > 0) { 10157 i--; 10158 spannable.removeSpan(spans[i]); 10159 } 10160 } 10161 10162 /** 10163 * Apply to this text view the given extracted text, as previously 10164 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 10165 */ 10166 public void setExtractedText(ExtractedText text) { 10167 Editable content = getEditableText(); 10168 if (text.text != null) { 10169 if (content == null) { 10170 setText(text.text, TextView.BufferType.EDITABLE); 10171 } else { 10172 int start = 0; 10173 int end = content.length(); 10174 10175 if (text.partialStartOffset >= 0) { 10176 final int N = content.length(); 10177 start = text.partialStartOffset; 10178 if (start > N) start = N; 10179 end = text.partialEndOffset; 10180 if (end > N) end = N; 10181 } 10182 10183 removeParcelableSpans(content, start, end); 10184 if (TextUtils.equals(content.subSequence(start, end), text.text)) { 10185 if (text.text instanceof Spanned) { 10186 // OK to copy spans only. 10187 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start, 10188 Object.class, content, start); 10189 } 10190 } else { 10191 content.replace(start, end, text.text); 10192 } 10193 } 10194 } 10195 10196 // Now set the selection position... make sure it is in range, to 10197 // avoid crashes. If this is a partial update, it is possible that 10198 // the underlying text may have changed, causing us problems here. 10199 // Also we just don't want to trust clients to do the right thing. 10200 Spannable sp = (Spannable) getText(); 10201 final int N = sp.length(); 10202 int start = text.selectionStart; 10203 if (start < 0) { 10204 start = 0; 10205 } else if (start > N) { 10206 start = N; 10207 } 10208 int end = text.selectionEnd; 10209 if (end < 0) { 10210 end = 0; 10211 } else if (end > N) { 10212 end = N; 10213 } 10214 Selection.setSelection(sp, start, end); 10215 10216 // Finally, update the selection mode. 10217 if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) { 10218 MetaKeyKeyListener.startSelecting(this, sp); 10219 } else { 10220 MetaKeyKeyListener.stopSelecting(this, sp); 10221 } 10222 10223 setHintInternal(text.hint); 10224 } 10225 10226 /** 10227 * @hide 10228 */ 10229 public void setExtracting(ExtractedTextRequest req) { 10230 if (mEditor.mInputMethodState != null) { 10231 mEditor.mInputMethodState.mExtractedTextRequest = req; 10232 } 10233 // This would stop a possible selection mode, but no such mode is started in case 10234 // extracted mode will start. Some text is selected though, and will trigger an action mode 10235 // in the extracted view. 10236 mEditor.hideCursorAndSpanControllers(); 10237 stopTextActionMode(); 10238 if (mEditor.mSelectionModifierCursorController != null) { 10239 mEditor.mSelectionModifierCursorController.resetTouchOffsets(); 10240 } 10241 } 10242 10243 /** 10244 * Called by the framework in response to a text completion from 10245 * the current input method, provided by it calling 10246 * {@link InputConnection#commitCompletion 10247 * InputConnection.commitCompletion()}. The default implementation does 10248 * nothing; text views that are supporting auto-completion should override 10249 * this to do their desired behavior. 10250 * 10251 * @param text The auto complete text the user has selected. 10252 */ 10253 public void onCommitCompletion(CompletionInfo text) { 10254 // intentionally empty 10255 } 10256 10257 /** 10258 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 10259 * dictionary) from the current input method, provided by it calling 10260 * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}. 10261 * The default implementation flashes the background of the corrected word to provide 10262 * feedback to the user. 10263 * 10264 * @param info The auto correct info about the text that was corrected. 10265 */ 10266 public void onCommitCorrection(CorrectionInfo info) { 10267 if (mEditor != null) mEditor.onCommitCorrection(info); 10268 } 10269 10270 public void beginBatchEdit() { 10271 if (mEditor != null) mEditor.beginBatchEdit(); 10272 } 10273 10274 public void endBatchEdit() { 10275 if (mEditor != null) mEditor.endBatchEdit(); 10276 } 10277 10278 /** 10279 * Called by the framework in response to a request to begin a batch 10280 * of edit operations through a call to link {@link #beginBatchEdit()}. 10281 */ 10282 public void onBeginBatchEdit() { 10283 // intentionally empty 10284 } 10285 10286 /** 10287 * Called by the framework in response to a request to end a batch 10288 * of edit operations through a call to link {@link #endBatchEdit}. 10289 */ 10290 public void onEndBatchEdit() { 10291 // intentionally empty 10292 } 10293 10294 /** @hide */ 10295 public void onPerformSpellCheck() { 10296 if (mEditor != null && mEditor.mSpellChecker != null) { 10297 mEditor.mSpellChecker.onPerformSpellCheck(); 10298 } 10299 } 10300 10301 /** 10302 * Called by the framework in response to a private command from the 10303 * current method, provided by it calling 10304 * {@link InputConnection#performPrivateCommand 10305 * InputConnection.performPrivateCommand()}. 10306 * 10307 * @param action The action name of the command. 10308 * @param data Any additional data for the command. This may be null. 10309 * @return Return true if you handled the command, else false. 10310 */ 10311 public boolean onPrivateIMECommand(String action, Bundle data) { 10312 return false; 10313 } 10314 10315 /** 10316 * Return whether the text is transformed and has {@link OffsetMapping}. 10317 * @hide 10318 */ 10319 public boolean isOffsetMappingAvailable() { 10320 return mTransformation != null && mTransformed instanceof OffsetMapping; 10321 } 10322 10323 /** @hide */ 10324 public boolean previewHandwritingGesture( 10325 @NonNull PreviewableHandwritingGesture gesture, 10326 @Nullable CancellationSignal cancellationSignal) { 10327 if (gesture instanceof SelectGesture) { 10328 performHandwritingSelectGesture((SelectGesture) gesture, /* isPreview= */ true); 10329 } else if (gesture instanceof SelectRangeGesture) { 10330 performHandwritingSelectRangeGesture( 10331 (SelectRangeGesture) gesture, /* isPreview= */ true); 10332 } else if (gesture instanceof DeleteGesture) { 10333 performHandwritingDeleteGesture((DeleteGesture) gesture, /* isPreview= */ true); 10334 } else if (gesture instanceof DeleteRangeGesture) { 10335 performHandwritingDeleteRangeGesture( 10336 (DeleteRangeGesture) gesture, /* isPreview= */ true); 10337 } else { 10338 return false; 10339 } 10340 if (cancellationSignal != null) { 10341 cancellationSignal.setOnCancelListener(this::clearGesturePreviewHighlight); 10342 } 10343 return true; 10344 } 10345 10346 /** @hide */ 10347 public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) { 10348 return performHandwritingSelectGesture(gesture, /* isPreview= */ false); 10349 } 10350 10351 private int performHandwritingSelectGesture(@NonNull SelectGesture gesture, boolean isPreview) { 10352 if (isOffsetMappingAvailable()) { 10353 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10354 } 10355 int[] range = getRangeForRect( 10356 convertFromScreenToContentCoordinates(gesture.getSelectionArea()), 10357 gesture.getGranularity()); 10358 if (range == null) { 10359 return handleGestureFailure(gesture, isPreview); 10360 } 10361 return performHandwritingSelectGesture(range, isPreview); 10362 } 10363 10364 private int performHandwritingSelectGesture(int[] range, boolean isPreview) { 10365 if (isPreview) { 10366 setSelectGesturePreviewHighlight(range[0], range[1]); 10367 } else { 10368 Selection.setSelection(getEditableText(), range[0], range[1]); 10369 mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false); 10370 } 10371 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10372 } 10373 10374 /** @hide */ 10375 public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) { 10376 return performHandwritingSelectRangeGesture(gesture, /* isPreview= */ false); 10377 } 10378 10379 private int performHandwritingSelectRangeGesture( 10380 @NonNull SelectRangeGesture gesture, boolean isPreview) { 10381 if (isOffsetMappingAvailable()) { 10382 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10383 } 10384 int[] startRange = getRangeForRect( 10385 convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()), 10386 gesture.getGranularity()); 10387 if (startRange == null) { 10388 return handleGestureFailure(gesture, isPreview); 10389 } 10390 int[] endRange = getRangeForRect( 10391 convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()), 10392 gesture.getGranularity()); 10393 if (endRange == null) { 10394 return handleGestureFailure(gesture, isPreview); 10395 } 10396 int[] range = new int[] { 10397 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1]) 10398 }; 10399 return performHandwritingSelectGesture(range, isPreview); 10400 } 10401 10402 /** @hide */ 10403 public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) { 10404 return performHandwritingDeleteGesture(gesture, /* isPreview= */ false); 10405 } 10406 10407 private int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture, boolean isPreview) { 10408 if (isOffsetMappingAvailable()) { 10409 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10410 } 10411 int[] range = getRangeForRect( 10412 convertFromScreenToContentCoordinates(gesture.getDeletionArea()), 10413 gesture.getGranularity()); 10414 if (range == null) { 10415 return handleGestureFailure(gesture, isPreview); 10416 } 10417 return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview); 10418 } 10419 10420 private int performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview) { 10421 if (isPreview) { 10422 setDeleteGesturePreviewHighlight(range[0], range[1]); 10423 } else { 10424 if (granularity == HandwritingGesture.GRANULARITY_WORD) { 10425 range = adjustHandwritingDeleteGestureRange(range); 10426 } 10427 10428 Selection.setSelection(getEditableText(), range[0]); 10429 getEditableText().delete(range[0], range[1]); 10430 } 10431 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10432 } 10433 10434 /** @hide */ 10435 public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) { 10436 return performHandwritingDeleteRangeGesture(gesture, /* isPreview= */ false); 10437 } 10438 10439 private int performHandwritingDeleteRangeGesture( 10440 @NonNull DeleteRangeGesture gesture, boolean isPreview) { 10441 if (isOffsetMappingAvailable()) { 10442 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10443 } 10444 int[] startRange = getRangeForRect( 10445 convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()), 10446 gesture.getGranularity()); 10447 if (startRange == null) { 10448 return handleGestureFailure(gesture, isPreview); 10449 } 10450 int[] endRange = getRangeForRect( 10451 convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()), 10452 gesture.getGranularity()); 10453 if (endRange == null) { 10454 return handleGestureFailure(gesture, isPreview); 10455 } 10456 int[] range = new int[] { 10457 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1]) 10458 }; 10459 return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview); 10460 } 10461 10462 private int[] adjustHandwritingDeleteGestureRange(int[] range) { 10463 // For handwriting delete gestures with word granularity, adjust the start and end offsets 10464 // to remove extra whitespace around the deleted text. 10465 10466 int start = range[0]; 10467 int end = range[1]; 10468 10469 // If the deleted text is at the start of the text, the behavior is the same as the case 10470 // where the deleted text follows a new line character. 10471 int codePointBeforeStart = start > 0 10472 ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT; 10473 // If the deleted text is at the end of the text, the behavior is the same as the case where 10474 // the deleted text precedes a new line character. 10475 int codePointAtEnd = end < mText.length() 10476 ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT; 10477 10478 if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart) 10479 && (TextUtils.isWhitespace(codePointAtEnd) 10480 || TextUtils.isPunctuation(codePointAtEnd))) { 10481 // Remove whitespace (except new lines) before the deleted text, in these cases: 10482 // - There is whitespace following the deleted text 10483 // e.g. "one [deleted] three" -> "one | three" -> "one| three" 10484 // - There is punctuation following the deleted text 10485 // e.g. "one [deleted]!" -> "one |!" -> "one|!" 10486 // - There is a new line following the deleted text 10487 // e.g. "one [deleted]\n" -> "one |\n" -> "one|\n" 10488 // - The deleted text is at the end of the text 10489 // e.g. "one [deleted]" -> "one |" -> "one|" 10490 // (The pipe | indicates the cursor position.) 10491 do { 10492 start -= Character.charCount(codePointBeforeStart); 10493 if (start == 0) break; 10494 codePointBeforeStart = Character.codePointBefore(mText, start); 10495 } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)); 10496 return new int[] {start, end}; 10497 } 10498 10499 if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd) 10500 && (TextUtils.isWhitespace(codePointBeforeStart) 10501 || TextUtils.isPunctuation(codePointBeforeStart))) { 10502 // Remove whitespace (except new lines) after the deleted text, in these cases: 10503 // - There is punctuation preceding the deleted text 10504 // e.g. "([deleted] two)" -> "(| two)" -> "(|two)" 10505 // - There is a new line preceding the deleted text 10506 // e.g. "\n[deleted] two" -> "\n| two" -> "\n|two" 10507 // - The deleted text is at the start of the text 10508 // e.g. "[deleted] two" -> "| two" -> "|two" 10509 // (The pipe | indicates the cursor position.) 10510 do { 10511 end += Character.charCount(codePointAtEnd); 10512 if (end == mText.length()) break; 10513 codePointAtEnd = Character.codePointAt(mText, end); 10514 } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)); 10515 return new int[] {start, end}; 10516 } 10517 10518 // Return the original range. 10519 return range; 10520 } 10521 10522 /** @hide */ 10523 public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) { 10524 if (isOffsetMappingAvailable()) { 10525 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10526 } 10527 PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint()); 10528 int line = getLineForHandwritingGesture(point); 10529 if (line == -1) { 10530 return handleGestureFailure(gesture); 10531 } 10532 int offset = mLayout.getOffsetForHorizontal(line, point.x); 10533 String textToInsert = gesture.getTextToInsert(); 10534 return tryInsertTextForHandwritingGesture(offset, textToInsert, gesture); 10535 // TODO(b/243980426): Insert extra spaces if necessary. 10536 } 10537 10538 /** @hide */ 10539 public int performHandwritingRemoveSpaceGesture(@NonNull RemoveSpaceGesture gesture) { 10540 if (isOffsetMappingAvailable()) { 10541 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10542 } 10543 PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint()); 10544 PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint()); 10545 10546 // The operation should be applied to the first line of text containing one of the points. 10547 int startPointLine = getLineForHandwritingGesture(startPoint); 10548 int endPointLine = getLineForHandwritingGesture(endPoint); 10549 int line; 10550 if (startPointLine == -1) { 10551 if (endPointLine == -1) { 10552 return handleGestureFailure(gesture); 10553 } 10554 line = endPointLine; 10555 } else { 10556 line = (endPointLine == -1) ? startPointLine : Math.min(startPointLine, endPointLine); 10557 } 10558 10559 // The operation should be applied to all characters touched by the line joining the points. 10560 float lineVerticalCenter = (mLayout.getLineTop(line) 10561 + mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) / 2f; 10562 // Create a rectangle which is +/-0.1f around the line's vertical center, so that the 10563 // rectangle doesn't touch the line above or below. (The line height is at least 1f.) 10564 RectF area = new RectF( 10565 Math.min(startPoint.x, endPoint.x), 10566 lineVerticalCenter + 0.1f, 10567 Math.max(startPoint.x, endPoint.x), 10568 lineVerticalCenter - 0.1f); 10569 int[] range = mLayout.getRangeForRect( 10570 area, new GraphemeClusterSegmentFinder(mText, mTextPaint), 10571 Layout.INCLUSION_STRATEGY_ANY_OVERLAP); 10572 if (range == null) { 10573 return handleGestureFailure(gesture); 10574 } 10575 int startOffset = range[0]; 10576 int endOffset = range[1]; 10577 // TODO(b/247557062): This doesn't handle bidirectional text correctly. 10578 10579 Pattern whitespacePattern = getWhitespacePattern(); 10580 Matcher matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset)); 10581 int lastRemoveOffset = -1; 10582 while (matcher.find()) { 10583 lastRemoveOffset = startOffset + matcher.start(); 10584 getEditableText().delete(lastRemoveOffset, startOffset + matcher.end()); 10585 startOffset = lastRemoveOffset; 10586 endOffset -= matcher.end() - matcher.start(); 10587 if (startOffset == endOffset) { 10588 break; 10589 } 10590 matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset)); 10591 } 10592 if (lastRemoveOffset == -1) { 10593 return handleGestureFailure(gesture); 10594 } 10595 Selection.setSelection(getEditableText(), lastRemoveOffset); 10596 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10597 } 10598 10599 /** @hide */ 10600 public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) { 10601 if (isOffsetMappingAvailable()) { 10602 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10603 } 10604 PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint()); 10605 10606 int line = getLineForHandwritingGesture(point); 10607 if (line == -1) { 10608 return handleGestureFailure(gesture); 10609 } 10610 10611 int startOffset = mLayout.getOffsetForHorizontal(line, point.x); 10612 if (mLayout.isLevelBoundary(startOffset)) { 10613 // TODO(b/247551937): Support gesture at level boundaries. 10614 return handleGestureFailure(gesture); 10615 } 10616 10617 int endOffset = startOffset; 10618 while (startOffset > 0) { 10619 int codePointBeforeStart = Character.codePointBefore(mText, startOffset); 10620 if (!TextUtils.isWhitespace(codePointBeforeStart)) { 10621 break; 10622 } 10623 startOffset -= Character.charCount(codePointBeforeStart); 10624 } 10625 while (endOffset < mText.length()) { 10626 int codePointAtEnd = Character.codePointAt(mText, endOffset); 10627 if (!TextUtils.isWhitespace(codePointAtEnd)) { 10628 break; 10629 } 10630 endOffset += Character.charCount(codePointAtEnd); 10631 } 10632 if (startOffset < endOffset) { 10633 Selection.setSelection(getEditableText(), startOffset); 10634 getEditableText().delete(startOffset, endOffset); 10635 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10636 } else { 10637 // No whitespace found, so insert a space. 10638 return tryInsertTextForHandwritingGesture(startOffset, " ", gesture); 10639 } 10640 } 10641 10642 /** @hide */ 10643 public int performHandwritingInsertModeGesture(@NonNull InsertModeGesture gesture) { 10644 final PointF insertPoint = 10645 convertFromScreenToContentCoordinates(gesture.getInsertionPoint()); 10646 final int line = getLineForHandwritingGesture(insertPoint); 10647 final CancellationSignal cancellationSignal = gesture.getCancellationSignal(); 10648 10649 // If no cancellationSignal is provided, don't enter the insert mode. 10650 if (line == -1 || cancellationSignal == null) { 10651 return handleGestureFailure(gesture); 10652 } 10653 10654 final int offset = mLayout.getOffsetForHorizontal(line, insertPoint.x); 10655 10656 if (!mEditor.enterInsertMode(offset)) { 10657 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10658 } 10659 cancellationSignal.setOnCancelListener(() -> mEditor.exitInsertMode()); 10660 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10661 } 10662 10663 private int handleGestureFailure(HandwritingGesture gesture) { 10664 return handleGestureFailure(gesture, /* isPreview= */ false); 10665 } 10666 10667 private int handleGestureFailure(HandwritingGesture gesture, boolean isPreview) { 10668 clearGesturePreviewHighlight(); 10669 if (!isPreview && !TextUtils.isEmpty(gesture.getFallbackText())) { 10670 getEditableText() 10671 .replace(getSelectionStart(), getSelectionEnd(), gesture.getFallbackText()); 10672 return InputConnection.HANDWRITING_GESTURE_RESULT_FALLBACK; 10673 } 10674 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10675 } 10676 10677 /** 10678 * Returns the closest line such that the point is either inside the line bounds or within 10679 * {@link ViewConfiguration#getScaledHandwritingGestureLineMargin} of the line bounds. Returns 10680 * -1 if the point is not within the margin of any line bounds. 10681 */ 10682 private int getLineForHandwritingGesture(PointF point) { 10683 int line = mLayout.getLineForVertical((int) point.y); 10684 int lineMargin = ViewConfiguration.get(mContext).getScaledHandwritingGestureLineMargin(); 10685 if (line < mLayout.getLineCount() - 1 10686 && point.y > mLayout.getLineBottom(line) - lineMargin 10687 && point.y 10688 > (mLayout.getLineBottom(line, false) + mLayout.getLineBottom(line)) / 2f) { 10689 // If a point is in the space between line i and line (i + 1), Layout#getLineForVertical 10690 // returns i. If the point is within lineMargin of line (i + 1), and closer to line 10691 // (i + 1) than line i, then the gesture operation should be applied to line (i + 1). 10692 line++; 10693 } else if (point.y < mLayout.getLineTop(line) - lineMargin 10694 || point.y 10695 > mLayout.getLineBottom(line, /* includeLineSpacing= */ false) 10696 + lineMargin) { 10697 // The point is not within lineMargin of a line. 10698 return -1; 10699 } 10700 if (point.x < -lineMargin || point.x > mLayout.getWidth() + lineMargin) { 10701 // The point is not within lineMargin of a line. 10702 return -1; 10703 } 10704 return line; 10705 } 10706 10707 @Nullable 10708 private int[] getRangeForRect(@NonNull RectF area, int granularity) { 10709 SegmentFinder segmentFinder; 10710 if (granularity == HandwritingGesture.GRANULARITY_WORD) { 10711 WordIterator wordIterator = getWordIterator(); 10712 wordIterator.setCharSequence(mText, 0, mText.length()); 10713 segmentFinder = new WordSegmentFinder(mText, wordIterator); 10714 } else { 10715 segmentFinder = new GraphemeClusterSegmentFinder(mText, mTextPaint); 10716 } 10717 10718 return mLayout.getRangeForRect( 10719 area, segmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER); 10720 } 10721 10722 private int tryInsertTextForHandwritingGesture( 10723 int offset, String textToInsert, HandwritingGesture gesture) { 10724 // A temporary cursor span is placed at the insertion offset. The span will be pushed 10725 // forward when text is inserted, then the real cursor can be placed after the inserted 10726 // text. A temporary cursor span is used in order to avoid modifying the real selection span 10727 // in the case that the text is filtered out. 10728 Editable editableText = getEditableText(); 10729 if (mTempCursor == null) { 10730 mTempCursor = new NoCopySpan.Concrete(); 10731 } 10732 editableText.setSpan(mTempCursor, offset, offset, Spanned.SPAN_POINT_POINT); 10733 10734 editableText.insert(offset, textToInsert); 10735 10736 int newOffset = editableText.getSpanStart(mTempCursor); 10737 editableText.removeSpan(mTempCursor); 10738 if (newOffset == offset) { 10739 // The inserted text was filtered out. 10740 return handleGestureFailure(gesture); 10741 } else { 10742 // Place the cursor after the inserted text. 10743 Selection.setSelection(editableText, newOffset); 10744 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10745 } 10746 } 10747 10748 private Pattern getWhitespacePattern() { 10749 if (mWhitespacePattern == null) { 10750 mWhitespacePattern = Pattern.compile("\\s+"); 10751 } 10752 return mWhitespacePattern; 10753 } 10754 10755 /** @hide */ 10756 @VisibleForTesting 10757 @UnsupportedAppUsage 10758 public void nullLayouts() { 10759 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 10760 mSavedLayout = (BoringLayout) mLayout; 10761 } 10762 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 10763 mSavedHintLayout = (BoringLayout) mHintLayout; 10764 } 10765 10766 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 10767 10768 mBoring = mHintBoring = null; 10769 10770 // Since it depends on the value of mLayout 10771 if (mEditor != null) mEditor.prepareCursorControllers(); 10772 } 10773 10774 /** 10775 * Make a new Layout based on the already-measured size of the view, 10776 * on the assumption that it was measured correctly at some point. 10777 */ 10778 @UnsupportedAppUsage 10779 private void assumeLayout() { 10780 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10781 10782 if (width < 1) { 10783 width = 0; 10784 } 10785 10786 int physicalWidth = width; 10787 10788 if (mHorizontallyScrolling) { 10789 width = VERY_WIDE; 10790 } 10791 10792 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 10793 physicalWidth, false); 10794 } 10795 10796 @UnsupportedAppUsage 10797 private Layout.Alignment getLayoutAlignment() { 10798 Layout.Alignment alignment; 10799 switch (getTextAlignment()) { 10800 case TEXT_ALIGNMENT_GRAVITY: 10801 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 10802 case Gravity.START: 10803 alignment = Layout.Alignment.ALIGN_NORMAL; 10804 break; 10805 case Gravity.END: 10806 alignment = Layout.Alignment.ALIGN_OPPOSITE; 10807 break; 10808 case Gravity.LEFT: 10809 alignment = Layout.Alignment.ALIGN_LEFT; 10810 break; 10811 case Gravity.RIGHT: 10812 alignment = Layout.Alignment.ALIGN_RIGHT; 10813 break; 10814 case Gravity.CENTER_HORIZONTAL: 10815 alignment = Layout.Alignment.ALIGN_CENTER; 10816 break; 10817 default: 10818 alignment = Layout.Alignment.ALIGN_NORMAL; 10819 break; 10820 } 10821 break; 10822 case TEXT_ALIGNMENT_TEXT_START: 10823 alignment = Layout.Alignment.ALIGN_NORMAL; 10824 break; 10825 case TEXT_ALIGNMENT_TEXT_END: 10826 alignment = Layout.Alignment.ALIGN_OPPOSITE; 10827 break; 10828 case TEXT_ALIGNMENT_CENTER: 10829 alignment = Layout.Alignment.ALIGN_CENTER; 10830 break; 10831 case TEXT_ALIGNMENT_VIEW_START: 10832 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 10833 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 10834 break; 10835 case TEXT_ALIGNMENT_VIEW_END: 10836 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 10837 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 10838 break; 10839 case TEXT_ALIGNMENT_INHERIT: 10840 // This should never happen as we have already resolved the text alignment 10841 // but better safe than sorry so we just fall through 10842 default: 10843 alignment = Layout.Alignment.ALIGN_NORMAL; 10844 break; 10845 } 10846 return alignment; 10847 } 10848 10849 private Paint.FontMetrics getResolvedMinimumFontMetrics() { 10850 if (mMinimumFontMetrics != null) { 10851 return mMinimumFontMetrics; 10852 } 10853 if (!mUseLocalePreferredLineHeightForMinimum) { 10854 return null; 10855 } 10856 10857 if (mLocalePreferredFontMetrics == null) { 10858 mLocalePreferredFontMetrics = new Paint.FontMetrics(); 10859 } 10860 mTextPaint.getFontMetricsForLocale(mLocalePreferredFontMetrics); 10861 return mLocalePreferredFontMetrics; 10862 } 10863 10864 /** 10865 * The width passed in is now the desired layout width, 10866 * not the full view width with padding. 10867 * {@hide} 10868 */ 10869 @VisibleForTesting 10870 @UnsupportedAppUsage 10871 public void makeNewLayout(int wantWidth, int hintWidth, 10872 BoringLayout.Metrics boring, 10873 BoringLayout.Metrics hintBoring, 10874 int ellipsisWidth, boolean bringIntoView) { 10875 stopMarquee(); 10876 10877 // Update "old" cached values 10878 mOldMaximum = mMaximum; 10879 mOldMaxMode = mMaxMode; 10880 10881 mHighlightPathBogus = true; 10882 mHighlightPathsBogus = true; 10883 10884 if (wantWidth < 0) { 10885 wantWidth = 0; 10886 } 10887 if (hintWidth < 0) { 10888 hintWidth = 0; 10889 } 10890 10891 Layout.Alignment alignment = getLayoutAlignment(); 10892 final boolean testDirChange = mSingleLine && mLayout != null 10893 && (alignment == Layout.Alignment.ALIGN_NORMAL 10894 || alignment == Layout.Alignment.ALIGN_OPPOSITE); 10895 int oldDir = 0; 10896 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 10897 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 10898 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE 10899 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 10900 TruncateAt effectiveEllipsize = mEllipsize; 10901 if (mEllipsize == TruncateAt.MARQUEE 10902 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 10903 effectiveEllipsize = TruncateAt.END_SMALL; 10904 } 10905 10906 if (mTextDir == null) { 10907 mTextDir = getTextDirectionHeuristic(); 10908 } 10909 10910 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 10911 effectiveEllipsize, effectiveEllipsize == mEllipsize); 10912 if (switchEllipsize) { 10913 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE 10914 ? TruncateAt.END : TruncateAt.MARQUEE; 10915 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 10916 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 10917 } 10918 10919 shouldEllipsize = mEllipsize != null; 10920 mHintLayout = null; 10921 10922 if (mHint != null) { 10923 if (shouldEllipsize) hintWidth = wantWidth; 10924 10925 if (hintBoring == UNKNOWN_BORING) { 10926 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 10927 isFallbackLineSpacingForBoringLayout(), 10928 getResolvedMinimumFontMetrics(), mHintBoring); 10929 10930 if (hintBoring != null) { 10931 mHintBoring = hintBoring; 10932 } 10933 } 10934 10935 if (hintBoring != null) { 10936 if (hintBoring.width <= hintWidth 10937 && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 10938 if (mSavedHintLayout != null) { 10939 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 10940 hintWidth, alignment, mSpacingMult, mSpacingAdd, 10941 hintBoring, mIncludePad); 10942 } else { 10943 mHintLayout = BoringLayout.make(mHint, mTextPaint, 10944 hintWidth, alignment, mSpacingMult, mSpacingAdd, 10945 hintBoring, mIncludePad); 10946 } 10947 10948 mSavedHintLayout = (BoringLayout) mHintLayout; 10949 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 10950 if (mSavedHintLayout != null) { 10951 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 10952 hintWidth, alignment, mSpacingMult, mSpacingAdd, 10953 hintBoring, mIncludePad, mEllipsize, 10954 ellipsisWidth); 10955 } else { 10956 mHintLayout = BoringLayout.make(mHint, mTextPaint, 10957 hintWidth, alignment, mSpacingMult, mSpacingAdd, 10958 hintBoring, mIncludePad, mEllipsize, 10959 ellipsisWidth); 10960 } 10961 } 10962 } 10963 // TODO: code duplication with makeSingleLayout() 10964 if (mHintLayout == null) { 10965 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, 10966 mHint.length(), mTextPaint, hintWidth) 10967 .setAlignment(alignment) 10968 .setTextDirection(mTextDir) 10969 .setLineSpacing(mSpacingAdd, mSpacingMult) 10970 .setIncludePad(mIncludePad) 10971 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 10972 .setBreakStrategy(mBreakStrategy) 10973 .setHyphenationFrequency(mHyphenationFrequency) 10974 .setJustificationMode(mJustificationMode) 10975 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 10976 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( 10977 mLineBreakStyle, mLineBreakWordStyle)) 10978 .setUseBoundsForWidth(mUseBoundsForWidth) 10979 .setMinimumFontMetrics(getResolvedMinimumFontMetrics()); 10980 10981 if (shouldEllipsize) { 10982 builder.setEllipsize(mEllipsize) 10983 .setEllipsizedWidth(ellipsisWidth); 10984 } 10985 mHintLayout = builder.build(); 10986 } 10987 } 10988 10989 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 10990 registerForPreDraw(); 10991 } 10992 10993 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 10994 if (!compressText(ellipsisWidth)) { 10995 final int height = mLayoutParams.height; 10996 // If the size of the view does not depend on the size of the text, try to 10997 // start the marquee immediately 10998 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 10999 startMarquee(); 11000 } else { 11001 // Defer the start of the marquee until we know our width (see setFrame()) 11002 mRestartMarquee = true; 11003 } 11004 } 11005 } 11006 11007 // CursorControllers need a non-null mLayout 11008 if (mEditor != null) mEditor.prepareCursorControllers(); 11009 } 11010 11011 /** 11012 * Returns true if DynamicLayout is required 11013 * 11014 * @hide 11015 */ 11016 @VisibleForTesting 11017 public boolean useDynamicLayout() { 11018 return isTextSelectable() || (mSpannable != null && mPrecomputed == null); 11019 } 11020 11021 /** 11022 * @hide 11023 */ 11024 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 11025 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 11026 boolean useSaved) { 11027 Layout result = null; 11028 if (useDynamicLayout()) { 11029 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint, 11030 wantWidth) 11031 .setDisplayText(mTransformed) 11032 .setAlignment(alignment) 11033 .setTextDirection(mTextDir) 11034 .setLineSpacing(mSpacingAdd, mSpacingMult) 11035 .setIncludePad(mIncludePad) 11036 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 11037 .setBreakStrategy(mBreakStrategy) 11038 .setHyphenationFrequency(mHyphenationFrequency) 11039 .setJustificationMode(mJustificationMode) 11040 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( 11041 mLineBreakStyle, mLineBreakWordStyle)) 11042 .setUseBoundsForWidth(mUseBoundsForWidth) 11043 .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null) 11044 .setEllipsizedWidth(ellipsisWidth) 11045 .setMinimumFontMetrics(getResolvedMinimumFontMetrics()); 11046 result = builder.build(); 11047 } else { 11048 if (boring == UNKNOWN_BORING) { 11049 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, 11050 isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(), 11051 mBoring); 11052 if (boring != null) { 11053 mBoring = boring; 11054 } 11055 } 11056 11057 if (boring != null) { 11058 if (boring.width <= wantWidth 11059 && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 11060 if (useSaved && mSavedLayout != null) { 11061 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 11062 wantWidth, alignment, mSpacingMult, mSpacingAdd, 11063 boring, mIncludePad, null, wantWidth, 11064 isFallbackLineSpacingForBoringLayout(), 11065 mUseBoundsForWidth, getResolvedMinimumFontMetrics()); 11066 } else { 11067 result = new BoringLayout( 11068 mTransformed, 11069 mTextPaint, 11070 wantWidth, 11071 alignment, 11072 mSpacingMult, 11073 mSpacingAdd, 11074 mIncludePad, 11075 isFallbackLineSpacingForBoringLayout(), 11076 wantWidth, 11077 null, 11078 boring, 11079 mUseBoundsForWidth, 11080 mShiftDrawingOffsetForStartOverhang, 11081 getResolvedMinimumFontMetrics()); 11082 } 11083 11084 if (useSaved) { 11085 mSavedLayout = (BoringLayout) result; 11086 } 11087 } else if (shouldEllipsize && boring.width <= wantWidth) { 11088 if (useSaved && mSavedLayout != null) { 11089 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 11090 wantWidth, alignment, mSpacingMult, mSpacingAdd, 11091 boring, mIncludePad, effectiveEllipsize, 11092 ellipsisWidth, isFallbackLineSpacingForBoringLayout(), 11093 mUseBoundsForWidth, getResolvedMinimumFontMetrics()); 11094 } else { 11095 result = new BoringLayout( 11096 mTransformed, 11097 mTextPaint, 11098 wantWidth, 11099 alignment, 11100 mSpacingMult, 11101 mSpacingAdd, 11102 mIncludePad, 11103 isFallbackLineSpacingForBoringLayout(), 11104 ellipsisWidth, 11105 effectiveEllipsize, 11106 boring, 11107 mUseBoundsForWidth, 11108 mShiftDrawingOffsetForStartOverhang, 11109 getResolvedMinimumFontMetrics()); 11110 } 11111 } 11112 } 11113 } 11114 if (result == null) { 11115 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 11116 0, mTransformed.length(), mTextPaint, wantWidth) 11117 .setAlignment(alignment) 11118 .setTextDirection(mTextDir) 11119 .setLineSpacing(mSpacingAdd, mSpacingMult) 11120 .setIncludePad(mIncludePad) 11121 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 11122 .setBreakStrategy(mBreakStrategy) 11123 .setHyphenationFrequency(mHyphenationFrequency) 11124 .setJustificationMode(mJustificationMode) 11125 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 11126 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( 11127 mLineBreakStyle, mLineBreakWordStyle)) 11128 .setUseBoundsForWidth(mUseBoundsForWidth) 11129 .setMinimumFontMetrics(getResolvedMinimumFontMetrics()); 11130 if (shouldEllipsize) { 11131 builder.setEllipsize(effectiveEllipsize) 11132 .setEllipsizedWidth(ellipsisWidth); 11133 } 11134 result = builder.build(); 11135 } 11136 return result; 11137 } 11138 11139 @UnsupportedAppUsage 11140 private boolean compressText(float width) { 11141 if (isHardwareAccelerated()) return false; 11142 11143 // Only compress the text if it hasn't been compressed by the previous pass 11144 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX 11145 && mTextPaint.getTextScaleX() == 1.0f) { 11146 final float textWidth = mLayout.getLineWidth(0); 11147 final float overflow = (textWidth + 1.0f - width) / width; 11148 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 11149 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 11150 post(new Runnable() { 11151 public void run() { 11152 requestLayout(); 11153 } 11154 }); 11155 return true; 11156 } 11157 } 11158 11159 return false; 11160 } 11161 11162 private static int desired(Layout layout, boolean useBoundsForWidth) { 11163 int n = layout.getLineCount(); 11164 CharSequence text = layout.getText(); 11165 float max = 0; 11166 11167 // if any line was wrapped, we can't use it. 11168 // but it's ok for the last line not to have a newline 11169 11170 for (int i = 0; i < n - 1; i++) { 11171 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') { 11172 return -1; 11173 } 11174 } 11175 11176 for (int i = 0; i < n; i++) { 11177 max = Math.max(max, layout.getLineMax(i)); 11178 } 11179 11180 if (useBoundsForWidth) { 11181 max = Math.max(max, layout.computeDrawingBoundingBox().width()); 11182 } 11183 11184 return (int) Math.ceil(max); 11185 } 11186 11187 /** 11188 * Set whether the TextView includes extra top and bottom padding to make 11189 * room for accents that go above the normal ascent and descent. 11190 * The default is true. 11191 * 11192 * @see #getIncludeFontPadding() 11193 * 11194 * @attr ref android.R.styleable#TextView_includeFontPadding 11195 */ 11196 public void setIncludeFontPadding(boolean includepad) { 11197 if (mIncludePad != includepad) { 11198 mIncludePad = includepad; 11199 11200 if (mLayout != null) { 11201 nullLayouts(); 11202 requestLayout(); 11203 invalidate(); 11204 } 11205 } 11206 } 11207 11208 /** 11209 * Gets whether the TextView includes extra top and bottom padding to make 11210 * room for accents that go above the normal ascent and descent. 11211 * 11212 * @see #setIncludeFontPadding(boolean) 11213 * 11214 * @attr ref android.R.styleable#TextView_includeFontPadding 11215 */ 11216 @InspectableProperty 11217 public boolean getIncludeFontPadding() { 11218 return mIncludePad; 11219 } 11220 11221 /** @hide */ 11222 @VisibleForTesting 11223 public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 11224 11225 @Override 11226 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 11227 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 11228 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 11229 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 11230 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 11231 11232 int width; 11233 int height; 11234 11235 BoringLayout.Metrics boring = UNKNOWN_BORING; 11236 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 11237 11238 if (mTextDir == null) { 11239 mTextDir = getTextDirectionHeuristic(); 11240 } 11241 11242 int des = -1; 11243 boolean fromexisting = false; 11244 final float widthLimit = (widthMode == MeasureSpec.AT_MOST) 11245 ? (float) widthSize : Float.MAX_VALUE; 11246 11247 if (widthMode == MeasureSpec.EXACTLY) { 11248 // Parent has told us how big to be. So be it. 11249 width = widthSize; 11250 } else { 11251 if (mLayout != null && mEllipsize == null) { 11252 des = desired(mLayout, mUseBoundsForWidth); 11253 } 11254 11255 if (des < 0) { 11256 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, 11257 isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(), 11258 mBoring); 11259 if (boring != null) { 11260 mBoring = boring; 11261 } 11262 } else { 11263 fromexisting = true; 11264 } 11265 11266 if (boring == null || boring == UNKNOWN_BORING) { 11267 if (des < 0) { 11268 des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0, 11269 mTransformed.length(), mTextPaint, mTextDir, widthLimit, 11270 mUseBoundsForWidth)); 11271 } 11272 width = des; 11273 } else { 11274 if (mUseBoundsForWidth) { 11275 RectF bbox = boring.getDrawingBoundingBox(); 11276 float rightMax = Math.max(bbox.right, boring.width); 11277 float leftMin = Math.min(bbox.left, 0); 11278 width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin)); 11279 } else { 11280 width = boring.width; 11281 } 11282 } 11283 11284 final Drawables dr = mDrawables; 11285 if (dr != null) { 11286 width = Math.max(width, dr.mDrawableWidthTop); 11287 width = Math.max(width, dr.mDrawableWidthBottom); 11288 } 11289 11290 if (mHint != null) { 11291 int hintDes = -1; 11292 int hintWidth; 11293 11294 if (mHintLayout != null && mEllipsize == null) { 11295 hintDes = desired(mHintLayout, mUseBoundsForWidth); 11296 } 11297 11298 if (hintDes < 0) { 11299 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 11300 isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(), 11301 mHintBoring); 11302 if (hintBoring != null) { 11303 mHintBoring = hintBoring; 11304 } 11305 } 11306 11307 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 11308 if (hintDes < 0) { 11309 hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0, 11310 mHint.length(), mTextPaint, mTextDir, widthLimit, 11311 mUseBoundsForWidth)); 11312 } 11313 hintWidth = hintDes; 11314 } else { 11315 hintWidth = hintBoring.width; 11316 } 11317 11318 if (hintWidth > width) { 11319 width = hintWidth; 11320 } 11321 } 11322 11323 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 11324 11325 if (mMaxWidthMode == EMS) { 11326 width = Math.min(width, mMaxWidth * getLineHeight()); 11327 } else { 11328 width = Math.min(width, mMaxWidth); 11329 } 11330 11331 if (mMinWidthMode == EMS) { 11332 width = Math.max(width, mMinWidth * getLineHeight()); 11333 } else { 11334 width = Math.max(width, mMinWidth); 11335 } 11336 11337 // Check against our minimum width 11338 width = Math.max(width, getSuggestedMinimumWidth()); 11339 11340 if (widthMode == MeasureSpec.AT_MOST) { 11341 width = Math.min(widthSize, width); 11342 } 11343 } 11344 11345 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 11346 int unpaddedWidth = want; 11347 11348 if (mHorizontallyScrolling) want = VERY_WIDE; 11349 11350 int hintWant = want; 11351 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 11352 11353 if (mLayout == null) { 11354 makeNewLayout(want, hintWant, boring, hintBoring, 11355 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 11356 } else { 11357 final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant) 11358 || (mLayout.getEllipsizedWidth() 11359 != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 11360 11361 final boolean widthChanged = (mHint == null) && (mEllipsize == null) 11362 && (want > mLayout.getWidth()) 11363 && (mLayout instanceof BoringLayout 11364 || (fromexisting && des >= 0 && des <= want)); 11365 11366 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 11367 11368 if (layoutChanged || maximumChanged) { 11369 if (!maximumChanged && widthChanged) { 11370 mLayout.increaseWidthTo(want); 11371 } else { 11372 makeNewLayout(want, hintWant, boring, hintBoring, 11373 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 11374 } 11375 } else { 11376 // Nothing has changed 11377 } 11378 } 11379 11380 if (heightMode == MeasureSpec.EXACTLY) { 11381 // Parent has told us how big to be. So be it. 11382 height = heightSize; 11383 mDesiredHeightAtMeasure = -1; 11384 } else { 11385 int desired = getDesiredHeight(); 11386 11387 height = desired; 11388 mDesiredHeightAtMeasure = desired; 11389 11390 if (heightMode == MeasureSpec.AT_MOST) { 11391 height = Math.min(desired, heightSize); 11392 } 11393 } 11394 11395 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 11396 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 11397 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 11398 } 11399 11400 /* 11401 * We didn't let makeNewLayout() register to bring the cursor into view, 11402 * so do it here if there is any possibility that it is needed. 11403 */ 11404 if (mMovement != null 11405 || mLayout.getWidth() > unpaddedWidth 11406 || mLayout.getHeight() > unpaddedHeight) { 11407 registerForPreDraw(); 11408 } else { 11409 scrollTo(0, 0); 11410 } 11411 11412 setMeasuredDimension(width, height); 11413 } 11414 11415 /** 11416 * Automatically computes and sets the text size. 11417 */ 11418 private void autoSizeText() { 11419 if (!isAutoSizeEnabled()) { 11420 return; 11421 } 11422 11423 if (mNeedsAutoSizeText) { 11424 if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) { 11425 return; 11426 } 11427 11428 final int availableWidth = mHorizontallyScrolling 11429 ? VERY_WIDE 11430 : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); 11431 final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom() 11432 - getExtendedPaddingTop(); 11433 11434 if (availableWidth <= 0 || availableHeight <= 0) { 11435 return; 11436 } 11437 11438 synchronized (TEMP_RECTF) { 11439 TEMP_RECTF.setEmpty(); 11440 TEMP_RECTF.right = availableWidth; 11441 TEMP_RECTF.bottom = availableHeight; 11442 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF); 11443 11444 if (optimalTextSize != getTextSize()) { 11445 setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize, 11446 false /* shouldRequestLayout */); 11447 11448 makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING, 11449 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 11450 false /* bringIntoView */); 11451 } 11452 } 11453 } 11454 // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing 11455 // after the next layout pass should set this to false. 11456 mNeedsAutoSizeText = true; 11457 } 11458 11459 /** 11460 * Performs a binary search to find the largest text size that will still fit within the size 11461 * available to this view. 11462 */ 11463 private int findLargestTextSizeWhichFits(RectF availableSpace) { 11464 final int sizesCount = mAutoSizeTextSizesInPx.length; 11465 if (sizesCount == 0) { 11466 throw new IllegalStateException("No available text sizes to choose from."); 11467 } 11468 11469 int bestSizeIndex = 0; 11470 int lowIndex = bestSizeIndex + 1; 11471 int highIndex = sizesCount - 1; 11472 int sizeToTryIndex; 11473 while (lowIndex <= highIndex) { 11474 sizeToTryIndex = (lowIndex + highIndex) / 2; 11475 if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) { 11476 bestSizeIndex = lowIndex; 11477 lowIndex = sizeToTryIndex + 1; 11478 } else { 11479 highIndex = sizeToTryIndex - 1; 11480 bestSizeIndex = highIndex; 11481 } 11482 } 11483 11484 return mAutoSizeTextSizesInPx[bestSizeIndex]; 11485 } 11486 11487 private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) { 11488 final CharSequence text = mTransformed != null 11489 ? mTransformed 11490 : getText(); 11491 final int maxLines = getMaxLines(); 11492 if (mTempTextPaint == null) { 11493 mTempTextPaint = new TextPaint(); 11494 } else { 11495 mTempTextPaint.reset(); 11496 } 11497 mTempTextPaint.set(getPaint()); 11498 mTempTextPaint.setTextSize(suggestedSizeInPx); 11499 11500 final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( 11501 text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); 11502 layoutBuilder.setAlignment(getLayoutAlignment()) 11503 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) 11504 .setIncludePad(getIncludeFontPadding()) 11505 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 11506 .setBreakStrategy(getBreakStrategy()) 11507 .setHyphenationFrequency(getHyphenationFrequency()) 11508 .setJustificationMode(getJustificationMode()) 11509 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 11510 .setTextDirection(getTextDirectionHeuristic()) 11511 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( 11512 mLineBreakStyle, mLineBreakWordStyle)) 11513 .setUseBoundsForWidth(mUseBoundsForWidth) 11514 .setMinimumFontMetrics(getResolvedMinimumFontMetrics()); 11515 11516 final StaticLayout layout = layoutBuilder.build(); 11517 11518 // Lines overflow. 11519 if (maxLines != -1 && layout.getLineCount() > maxLines) { 11520 return false; 11521 } 11522 11523 // Height overflow. 11524 if (layout.getHeight() > availableSpace.bottom) { 11525 return false; 11526 } 11527 11528 return true; 11529 } 11530 11531 private int getDesiredHeight() { 11532 return Math.max( 11533 getDesiredHeight(mLayout, true), 11534 getDesiredHeight(mHintLayout, mEllipsize != null)); 11535 } 11536 11537 private int getDesiredHeight(Layout layout, boolean cap) { 11538 if (layout == null) { 11539 return 0; 11540 } 11541 11542 /* 11543 * Don't cap the hint to a certain number of lines. 11544 * (Do cap it, though, if we have a maximum pixel height.) 11545 */ 11546 int desired = layout.getHeight(cap); 11547 11548 final Drawables dr = mDrawables; 11549 if (dr != null) { 11550 desired = Math.max(desired, dr.mDrawableHeightLeft); 11551 desired = Math.max(desired, dr.mDrawableHeightRight); 11552 } 11553 11554 int linecount = layout.getLineCount(); 11555 final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom(); 11556 desired += padding; 11557 11558 if (mMaxMode != LINES) { 11559 desired = Math.min(desired, mMaximum); 11560 } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout 11561 || layout instanceof BoringLayout)) { 11562 desired = layout.getLineTop(mMaximum); 11563 11564 if (dr != null) { 11565 desired = Math.max(desired, dr.mDrawableHeightLeft); 11566 desired = Math.max(desired, dr.mDrawableHeightRight); 11567 } 11568 11569 desired += padding; 11570 linecount = mMaximum; 11571 } 11572 11573 if (mMinMode == LINES) { 11574 if (linecount < mMinimum) { 11575 desired += getLineHeight() * (mMinimum - linecount); 11576 } 11577 } else { 11578 desired = Math.max(desired, mMinimum); 11579 } 11580 11581 // Check against our minimum height 11582 desired = Math.max(desired, getSuggestedMinimumHeight()); 11583 11584 return desired; 11585 } 11586 11587 /** 11588 * Check whether a change to the existing text layout requires a 11589 * new view layout. 11590 */ 11591 private void checkForResize() { 11592 boolean sizeChanged = false; 11593 11594 if (mLayout != null) { 11595 // Check if our width changed 11596 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 11597 sizeChanged = true; 11598 invalidate(); 11599 } 11600 11601 // Check if our height changed 11602 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 11603 int desiredHeight = getDesiredHeight(); 11604 11605 if (desiredHeight != this.getHeight()) { 11606 sizeChanged = true; 11607 } 11608 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 11609 if (mDesiredHeightAtMeasure >= 0) { 11610 int desiredHeight = getDesiredHeight(); 11611 11612 if (desiredHeight != mDesiredHeightAtMeasure) { 11613 sizeChanged = true; 11614 } 11615 } 11616 } 11617 } 11618 11619 if (sizeChanged) { 11620 requestLayout(); 11621 // caller will have already invalidated 11622 } 11623 } 11624 11625 /** 11626 * Check whether entirely new text requires a new view layout 11627 * or merely a new text layout. 11628 */ 11629 @UnsupportedAppUsage 11630 private void checkForRelayout() { 11631 // If we have a fixed width, we can just swap in a new text layout 11632 // if the text height stays the same or if the view height is fixed. 11633 11634 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT 11635 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) 11636 && (mHint == null || mHintLayout != null) 11637 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 11638 // Static width, so try making a new text layout. 11639 11640 int oldht = mLayout.getHeight(); 11641 int want = mLayout.getWidth(); 11642 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 11643 11644 /* 11645 * No need to bring the text into view, since the size is not 11646 * changing (unless we do the requestLayout(), in which case it 11647 * will happen at measure). 11648 */ 11649 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 11650 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 11651 false); 11652 11653 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 11654 // In a fixed-height view, so use our new text layout. 11655 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT 11656 && mLayoutParams.height != LayoutParams.MATCH_PARENT) { 11657 autoSizeText(); 11658 invalidate(); 11659 return; 11660 } 11661 11662 // Dynamic height, but height has stayed the same, 11663 // so use our new text layout. 11664 if (mLayout.getHeight() == oldht 11665 && (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 11666 autoSizeText(); 11667 invalidate(); 11668 return; 11669 } 11670 } 11671 11672 // We lose: the height has changed and we have a dynamic height. 11673 // Request a new view layout using our new text layout. 11674 requestLayout(); 11675 invalidate(); 11676 } else { 11677 // Dynamic width, so we have no choice but to request a new 11678 // view layout with a new text layout. 11679 nullLayouts(); 11680 requestLayout(); 11681 invalidate(); 11682 } 11683 } 11684 11685 @Override 11686 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 11687 super.onLayout(changed, left, top, right, bottom); 11688 if (mDeferScroll >= 0) { 11689 int curs = mDeferScroll; 11690 mDeferScroll = -1; 11691 bringPointIntoView(Math.min(curs, mText.length())); 11692 } 11693 // Call auto-size after the width and height have been calculated. 11694 autoSizeText(); 11695 } 11696 11697 private boolean isShowingHint() { 11698 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint) && !mHideHint; 11699 } 11700 11701 /** 11702 * Returns true if anything changed. 11703 */ 11704 @UnsupportedAppUsage 11705 private boolean bringTextIntoView() { 11706 Layout layout = isShowingHint() ? mHintLayout : mLayout; 11707 int line = 0; 11708 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 11709 line = layout.getLineCount() - 1; 11710 } 11711 11712 Layout.Alignment a = layout.getParagraphAlignment(line); 11713 int dir = layout.getParagraphDirection(line); 11714 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 11715 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 11716 int ht = layout.getHeight(); 11717 11718 int scrollx, scrolly; 11719 11720 // Convert to left, center, or right alignment. 11721 if (a == Layout.Alignment.ALIGN_NORMAL) { 11722 a = dir == Layout.DIR_LEFT_TO_RIGHT 11723 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 11724 } else if (a == Layout.Alignment.ALIGN_OPPOSITE) { 11725 a = dir == Layout.DIR_LEFT_TO_RIGHT 11726 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 11727 } 11728 11729 if (a == Layout.Alignment.ALIGN_CENTER) { 11730 /* 11731 * Keep centered if possible, or, if it is too wide to fit, 11732 * keep leading edge in view. 11733 */ 11734 11735 int left = (int) Math.floor(layout.getLineLeft(line)); 11736 int right = (int) Math.ceil(layout.getLineRight(line)); 11737 11738 if (right - left < hspace) { 11739 scrollx = (right + left) / 2 - hspace / 2; 11740 } else { 11741 if (dir < 0) { 11742 scrollx = right - hspace; 11743 } else { 11744 scrollx = left; 11745 } 11746 } 11747 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 11748 int right = (int) Math.ceil(layout.getLineRight(line)); 11749 scrollx = right - hspace; 11750 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 11751 scrollx = (int) Math.floor(layout.getLineLeft(line)); 11752 } 11753 11754 if (ht < vspace) { 11755 scrolly = 0; 11756 } else { 11757 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 11758 scrolly = ht - vspace; 11759 } else { 11760 scrolly = 0; 11761 } 11762 } 11763 11764 if (scrollx != mScrollX || scrolly != mScrollY) { 11765 scrollTo(scrollx, scrolly); 11766 return true; 11767 } else { 11768 return false; 11769 } 11770 } 11771 11772 /** 11773 * Move the point, specified by the offset, into the view if it is needed. 11774 * This has to be called after layout. Returns true if anything changed. 11775 */ 11776 public boolean bringPointIntoView(int offset) { 11777 return bringPointIntoView(offset, false); 11778 } 11779 11780 /** 11781 * Move the insertion position of the given offset into visible area of the View. 11782 * 11783 * If the View is focused or {@code requestRectWithoutFocus} is set to true, this API may call 11784 * {@link View#requestRectangleOnScreen(Rect)} to bring the point to the visible area if 11785 * necessary. 11786 * 11787 * @param offset an offset of the character. 11788 * @param requestRectWithoutFocus True for calling {@link View#requestRectangleOnScreen(Rect)} 11789 * in the unfocused state. False for calling it only the View has 11790 * the focus. 11791 * @return true if anything changed, otherwise false. 11792 * 11793 * @see #bringPointIntoView(int) 11794 */ 11795 public boolean bringPointIntoView(@IntRange(from = 0) int offset, 11796 boolean requestRectWithoutFocus) { 11797 if (isLayoutRequested()) { 11798 mDeferScroll = offset; 11799 return false; 11800 } 11801 final int offsetTransformed = 11802 originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR); 11803 boolean changed = false; 11804 11805 Layout layout = isShowingHint() ? mHintLayout : mLayout; 11806 11807 if (layout == null) return changed; 11808 11809 int line = layout.getLineForOffset(offsetTransformed); 11810 11811 int grav; 11812 11813 switch (layout.getParagraphAlignment(line)) { 11814 case ALIGN_LEFT: 11815 grav = 1; 11816 break; 11817 case ALIGN_RIGHT: 11818 grav = -1; 11819 break; 11820 case ALIGN_NORMAL: 11821 grav = layout.getParagraphDirection(line); 11822 break; 11823 case ALIGN_OPPOSITE: 11824 grav = -layout.getParagraphDirection(line); 11825 break; 11826 case ALIGN_CENTER: 11827 default: 11828 grav = 0; 11829 break; 11830 } 11831 11832 // We only want to clamp the cursor to fit within the layout width 11833 // in left-to-right modes, because in a right to left alignment, 11834 // we want to scroll to keep the line-right on the screen, as other 11835 // lines are likely to have text flush with the right margin, which 11836 // we want to keep visible. 11837 // A better long-term solution would probably be to measure both 11838 // the full line and a blank-trimmed version, and, for example, use 11839 // the latter measurement for centering and right alignment, but for 11840 // the time being we only implement the cursor clamping in left to 11841 // right where it is most likely to be annoying. 11842 final boolean clamped = grav > 0; 11843 // FIXME: Is it okay to truncate this, or should we round? 11844 final int x = (int) layout.getPrimaryHorizontal(offsetTransformed, clamped); 11845 final int top = layout.getLineTop(line); 11846 final int bottom = layout.getLineTop(line + 1); 11847 11848 int left = (int) Math.floor(layout.getLineLeft(line)); 11849 int right = (int) Math.ceil(layout.getLineRight(line)); 11850 int ht = layout.getHeight(); 11851 11852 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 11853 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 11854 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 11855 // If cursor has been clamped, make sure we don't scroll. 11856 right = Math.max(x, left + hspace); 11857 } 11858 11859 int hslack = (bottom - top) / 2; 11860 int vslack = hslack; 11861 11862 if (vslack > vspace / 4) { 11863 vslack = vspace / 4; 11864 } 11865 if (hslack > hspace / 4) { 11866 hslack = hspace / 4; 11867 } 11868 11869 int hs = mScrollX; 11870 int vs = mScrollY; 11871 11872 if (top - vs < vslack) { 11873 vs = top - vslack; 11874 } 11875 if (bottom - vs > vspace - vslack) { 11876 vs = bottom - (vspace - vslack); 11877 } 11878 if (ht - vs < vspace) { 11879 vs = ht - vspace; 11880 } 11881 if (0 - vs > 0) { 11882 vs = 0; 11883 } 11884 11885 if (grav != 0) { 11886 if (x - hs < hslack) { 11887 hs = x - hslack; 11888 } 11889 if (x - hs > hspace - hslack) { 11890 hs = x - (hspace - hslack); 11891 } 11892 } 11893 11894 if (grav < 0) { 11895 if (left - hs > 0) { 11896 hs = left; 11897 } 11898 if (right - hs < hspace) { 11899 hs = right - hspace; 11900 } 11901 } else if (grav > 0) { 11902 if (right - hs < hspace) { 11903 hs = right - hspace; 11904 } 11905 if (left - hs > 0) { 11906 hs = left; 11907 } 11908 } else /* grav == 0 */ { 11909 if (right - left <= hspace) { 11910 /* 11911 * If the entire text fits, center it exactly. 11912 */ 11913 hs = left - (hspace - (right - left)) / 2; 11914 } else if (x > right - hslack) { 11915 /* 11916 * If we are near the right edge, keep the right edge 11917 * at the edge of the view. 11918 */ 11919 hs = right - hspace; 11920 } else if (x < left + hslack) { 11921 /* 11922 * If we are near the left edge, keep the left edge 11923 * at the edge of the view. 11924 */ 11925 hs = left; 11926 } else if (left > hs) { 11927 /* 11928 * Is there whitespace visible at the left? Fix it if so. 11929 */ 11930 hs = left; 11931 } else if (right < hs + hspace) { 11932 /* 11933 * Is there whitespace visible at the right? Fix it if so. 11934 */ 11935 hs = right - hspace; 11936 } else { 11937 /* 11938 * Otherwise, float as needed. 11939 */ 11940 if (x - hs < hslack) { 11941 hs = x - hslack; 11942 } 11943 if (x - hs > hspace - hslack) { 11944 hs = x - (hspace - hslack); 11945 } 11946 } 11947 } 11948 11949 if (hs != mScrollX || vs != mScrollY) { 11950 if (mScroller == null) { 11951 scrollTo(hs, vs); 11952 } else { 11953 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 11954 int dx = hs - mScrollX; 11955 int dy = vs - mScrollY; 11956 11957 if (duration > ANIMATED_SCROLL_GAP) { 11958 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 11959 awakenScrollBars(mScroller.getDuration()); 11960 invalidate(); 11961 } else { 11962 if (!mScroller.isFinished()) { 11963 mScroller.abortAnimation(); 11964 } 11965 11966 scrollBy(dx, dy); 11967 } 11968 11969 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 11970 } 11971 11972 changed = true; 11973 } 11974 11975 if (requestRectWithoutFocus || isFocused()) { 11976 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 11977 // requestRectangleOnScreen() is in terms of content coordinates. 11978 11979 // The offsets here are to ensure the rectangle we are using is 11980 // within our view bounds, in case the cursor is on the far left 11981 // or right. If it isn't withing the bounds, then this request 11982 // will be ignored. 11983 if (mTempRect == null) mTempRect = new Rect(); 11984 mTempRect.set(x - 2, top, x + 2, bottom); 11985 getInterestingRect(mTempRect, line); 11986 mTempRect.offset(mScrollX, mScrollY); 11987 11988 if (requestRectangleOnScreen(mTempRect)) { 11989 changed = true; 11990 } 11991 } 11992 11993 return changed; 11994 } 11995 11996 /** 11997 * Move the cursor, if needed, so that it is at an offset that is visible 11998 * to the user. This will not move the cursor if it represents more than 11999 * one character (a selection range). This will only work if the 12000 * TextView contains spannable text; otherwise it will do nothing. 12001 * 12002 * @return True if the cursor was actually moved, false otherwise. 12003 */ 12004 public boolean moveCursorToVisibleOffset() { 12005 if (!(mText instanceof Spannable)) { 12006 return false; 12007 } 12008 int start = getSelectionStartTransformed(); 12009 int end = getSelectionEndTransformed(); 12010 if (start != end) { 12011 return false; 12012 } 12013 12014 // First: make sure the line is visible on screen: 12015 12016 int line = mLayout.getLineForOffset(start); 12017 12018 final int top = mLayout.getLineTop(line); 12019 final int bottom = mLayout.getLineTop(line + 1); 12020 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 12021 int vslack = (bottom - top) / 2; 12022 if (vslack > vspace / 4) { 12023 vslack = vspace / 4; 12024 } 12025 final int vs = mScrollY; 12026 12027 if (top < (vs + vslack)) { 12028 line = mLayout.getLineForVertical(vs + vslack + (bottom - top)); 12029 } else if (bottom > (vspace + vs - vslack)) { 12030 line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top)); 12031 } 12032 12033 // Next: make sure the character is visible on screen: 12034 12035 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 12036 final int hs = mScrollX; 12037 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 12038 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs); 12039 12040 // line might contain bidirectional text 12041 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 12042 final int highChar = leftChar > rightChar ? leftChar : rightChar; 12043 12044 int newStart = start; 12045 if (newStart < lowChar) { 12046 newStart = lowChar; 12047 } else if (newStart > highChar) { 12048 newStart = highChar; 12049 } 12050 12051 if (newStart != start) { 12052 Selection.setSelection(mSpannable, 12053 transformedToOriginal(newStart, OffsetMapping.MAP_STRATEGY_CURSOR)); 12054 return true; 12055 } 12056 12057 return false; 12058 } 12059 12060 @Override 12061 public void computeScroll() { 12062 if (mScroller != null) { 12063 if (mScroller.computeScrollOffset()) { 12064 mScrollX = mScroller.getCurrX(); 12065 mScrollY = mScroller.getCurrY(); 12066 invalidateParentCaches(); 12067 postInvalidate(); // So we draw again 12068 } 12069 } 12070 } 12071 12072 private void getInterestingRect(Rect r, int line) { 12073 convertFromViewportToContentCoordinates(r); 12074 12075 // Rectangle can can be expanded on first and last line to take 12076 // padding into account. 12077 // TODO Take left/right padding into account too? 12078 if (line == 0) r.top -= getExtendedPaddingTop(); 12079 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 12080 } 12081 12082 private void convertFromViewportToContentCoordinates(Rect r) { 12083 final int horizontalOffset = viewportToContentHorizontalOffset(); 12084 r.left += horizontalOffset; 12085 r.right += horizontalOffset; 12086 12087 final int verticalOffset = viewportToContentVerticalOffset(); 12088 r.top += verticalOffset; 12089 r.bottom += verticalOffset; 12090 } 12091 12092 private PointF convertFromScreenToContentCoordinates(PointF point) { 12093 int[] screenToViewport = getLocationOnScreen(); 12094 PointF copy = new PointF(point); 12095 copy.offset( 12096 -(screenToViewport[0] + viewportToContentHorizontalOffset()), 12097 -(screenToViewport[1] + viewportToContentVerticalOffset())); 12098 return copy; 12099 } 12100 12101 private RectF convertFromScreenToContentCoordinates(RectF rect) { 12102 int[] screenToViewport = getLocationOnScreen(); 12103 RectF copy = new RectF(rect); 12104 copy.offset( 12105 -(screenToViewport[0] + viewportToContentHorizontalOffset()), 12106 -(screenToViewport[1] + viewportToContentVerticalOffset())); 12107 return copy; 12108 } 12109 12110 int viewportToContentHorizontalOffset() { 12111 return getCompoundPaddingLeft() - mScrollX; 12112 } 12113 12114 @UnsupportedAppUsage 12115 int viewportToContentVerticalOffset() { 12116 int offset = getExtendedPaddingTop() - mScrollY; 12117 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 12118 offset += getVerticalOffset(false); 12119 } 12120 return offset; 12121 } 12122 12123 @Override 12124 public void debug(int depth) { 12125 super.debug(depth); 12126 12127 String output = debugIndent(depth); 12128 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 12129 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 12130 + "} "; 12131 12132 if (mText != null) { 12133 12134 output += "mText=\"" + mText + "\" "; 12135 if (mLayout != null) { 12136 output += "mLayout width=" + mLayout.getWidth() 12137 + " height=" + mLayout.getHeight(); 12138 } 12139 } else { 12140 output += "mText=NULL"; 12141 } 12142 Log.d(VIEW_LOG_TAG, output); 12143 } 12144 12145 /** 12146 * Convenience for {@link Selection#getSelectionStart}. 12147 */ 12148 @ViewDebug.ExportedProperty(category = "text") 12149 public int getSelectionStart() { 12150 return Selection.getSelectionStart(getText()); 12151 } 12152 12153 /** 12154 * Convenience for {@link Selection#getSelectionEnd}. 12155 */ 12156 @ViewDebug.ExportedProperty(category = "text") 12157 public int getSelectionEnd() { 12158 return Selection.getSelectionEnd(getText()); 12159 } 12160 12161 /** 12162 * Calculates the rectangles which should be highlighted to indicate a selection between start 12163 * and end and feeds them into the given {@link Layout.SelectionRectangleConsumer}. 12164 * 12165 * @param start the starting index of the selection 12166 * @param end the ending index of the selection 12167 * @param consumer the {@link Layout.SelectionRectangleConsumer} which will receive the 12168 * generated rectangles. It will be called every time a rectangle is generated. 12169 * @hide 12170 */ 12171 public void getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer) { 12172 final int transformedStart = 12173 originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR); 12174 final int transformedEnd = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR); 12175 mLayout.getSelection(transformedStart, transformedEnd, consumer); 12176 } 12177 12178 int getSelectionStartTransformed() { 12179 final int start = getSelectionStart(); 12180 if (start < 0) return start; 12181 return originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR); 12182 } 12183 12184 int getSelectionEndTransformed() { 12185 final int end = getSelectionEnd(); 12186 if (end < 0) return end; 12187 return originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR); 12188 } 12189 12190 /** 12191 * Return true iff there is a selection of nonzero length inside this text view. 12192 */ 12193 public boolean hasSelection() { 12194 final int selectionStart = getSelectionStart(); 12195 final int selectionEnd = getSelectionEnd(); 12196 final int selectionMin; 12197 final int selectionMax; 12198 if (selectionStart < selectionEnd) { 12199 selectionMin = selectionStart; 12200 selectionMax = selectionEnd; 12201 } else { 12202 selectionMin = selectionEnd; 12203 selectionMax = selectionStart; 12204 } 12205 12206 return selectionMin >= 0 && selectionMax > 0 && selectionMin != selectionMax; 12207 } 12208 12209 String getSelectedText() { 12210 if (!hasSelection()) { 12211 return null; 12212 } 12213 12214 final int start = getSelectionStart(); 12215 final int end = getSelectionEnd(); 12216 return String.valueOf( 12217 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end)); 12218 } 12219 12220 /** 12221 * Sets the properties of this field (lines, horizontally scrolling, 12222 * transformation method) to be for a single-line input. 12223 * 12224 * @attr ref android.R.styleable#TextView_singleLine 12225 */ 12226 public void setSingleLine() { 12227 setSingleLine(true); 12228 } 12229 12230 /** 12231 * Sets the properties of this field to transform input to ALL CAPS 12232 * display. This may use a "small caps" formatting if available. 12233 * This setting will be ignored if this field is editable or selectable. 12234 * 12235 * This call replaces the current transformation method. Disabling this 12236 * will not necessarily restore the previous behavior from before this 12237 * was enabled. 12238 * 12239 * @see #setTransformationMethod(TransformationMethod) 12240 * @attr ref android.R.styleable#TextView_textAllCaps 12241 */ 12242 @android.view.RemotableViewMethod 12243 public void setAllCaps(boolean allCaps) { 12244 if (allCaps) { 12245 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 12246 } else { 12247 setTransformationMethod(null); 12248 } 12249 } 12250 12251 /** 12252 * 12253 * Checks whether the transformation method applied to this TextView is set to ALL CAPS. 12254 * @return Whether the current transformation method is for ALL CAPS. 12255 * 12256 * @see #setAllCaps(boolean) 12257 * @see #setTransformationMethod(TransformationMethod) 12258 */ 12259 @InspectableProperty(name = "textAllCaps") 12260 public boolean isAllCaps() { 12261 final TransformationMethod method = getTransformationMethod(); 12262 return method != null && method instanceof AllCapsTransformationMethod; 12263 } 12264 12265 /** 12266 * If true, sets the properties of this field (number of lines, horizontally scrolling, 12267 * transformation method) to be for a single-line input; if false, restores these to the default 12268 * conditions. 12269 * 12270 * Note that the default conditions are not necessarily those that were in effect prior this 12271 * method, and you may want to reset these properties to your custom values. 12272 * 12273 * Note that due to performance reasons, by setting single line for the EditText, the maximum 12274 * text length is set to 5000 if no other character limitation are applied. 12275 * 12276 * @attr ref android.R.styleable#TextView_singleLine 12277 */ 12278 @android.view.RemotableViewMethod 12279 public void setSingleLine(boolean singleLine) { 12280 // Could be used, but may break backward compatibility. 12281 // if (mSingleLine == singleLine) return; 12282 setInputTypeSingleLine(singleLine); 12283 applySingleLine(singleLine, true, true, true); 12284 } 12285 12286 /** 12287 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 12288 * @param singleLine 12289 */ 12290 private void setInputTypeSingleLine(boolean singleLine) { 12291 if (mEditor != null 12292 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 12293 == EditorInfo.TYPE_CLASS_TEXT) { 12294 if (singleLine) { 12295 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 12296 } else { 12297 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 12298 } 12299 } 12300 } 12301 12302 private void applySingleLine(boolean singleLine, boolean applyTransformation, 12303 boolean changeMaxLines, boolean changeMaxLength) { 12304 mSingleLine = singleLine; 12305 12306 if (singleLine) { 12307 setLines(1); 12308 setHorizontallyScrolling(true); 12309 if (applyTransformation) { 12310 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 12311 } 12312 12313 if (!changeMaxLength) return; 12314 12315 // Single line length filter is only applicable editable text. 12316 if (mBufferType != BufferType.EDITABLE) return; 12317 12318 final InputFilter[] prevFilters = getFilters(); 12319 for (InputFilter filter: getFilters()) { 12320 // We don't add LengthFilter if already there. 12321 if (filter instanceof InputFilter.LengthFilter) return; 12322 } 12323 12324 if (mSingleLineLengthFilter == null) { 12325 mSingleLineLengthFilter = new InputFilter.LengthFilter( 12326 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT); 12327 } 12328 12329 final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1]; 12330 System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length); 12331 newFilters[prevFilters.length] = mSingleLineLengthFilter; 12332 12333 setFilters(newFilters); 12334 12335 // Since filter doesn't apply to existing text, trigger filter by setting text. 12336 setText(getText()); 12337 } else { 12338 if (changeMaxLines) { 12339 setMaxLines(Integer.MAX_VALUE); 12340 } 12341 setHorizontallyScrolling(false); 12342 if (applyTransformation) { 12343 setTransformationMethod(null); 12344 } 12345 12346 if (!changeMaxLength) return; 12347 12348 // Single line length filter is only applicable editable text. 12349 if (mBufferType != BufferType.EDITABLE) return; 12350 12351 final InputFilter[] prevFilters = getFilters(); 12352 if (prevFilters.length == 0) return; 12353 12354 // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated 12355 // single line char limit filter. 12356 if (mSingleLineLengthFilter == null) return; 12357 12358 // If we need to remove mSingleLineLengthFilter, we need to allocate another array. 12359 // Since filter list is expected to be small and want to avoid unnecessary array 12360 // allocation, check if there is mSingleLengthFilter first. 12361 int targetIndex = -1; 12362 for (int i = 0; i < prevFilters.length; ++i) { 12363 if (prevFilters[i] == mSingleLineLengthFilter) { 12364 targetIndex = i; 12365 break; 12366 } 12367 } 12368 if (targetIndex == -1) return; // not found. Do nothing. 12369 12370 if (prevFilters.length == 1) { 12371 setFilters(NO_FILTERS); 12372 return; 12373 } 12374 12375 // Create new array which doesn't include mSingleLengthFilter. 12376 final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1]; 12377 System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex); 12378 System.arraycopy( 12379 prevFilters, 12380 targetIndex + 1, 12381 newFilters, 12382 targetIndex, 12383 prevFilters.length - targetIndex - 1); 12384 setFilters(newFilters); 12385 mSingleLineLengthFilter = null; 12386 } 12387 } 12388 12389 /** 12390 * Causes words in the text that are longer than the view's width 12391 * to be ellipsized instead of broken in the middle. You may also 12392 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 12393 * to constrain the text to a single line. Use <code>null</code> 12394 * to turn off ellipsizing. 12395 * 12396 * If {@link #setMaxLines} has been used to set two or more lines, 12397 * only {@link android.text.TextUtils.TruncateAt#END} and 12398 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 12399 * (other ellipsizing types will not do anything). 12400 * 12401 * @attr ref android.R.styleable#TextView_ellipsize 12402 */ 12403 public void setEllipsize(TextUtils.TruncateAt where) { 12404 // TruncateAt is an enum. != comparison is ok between these singleton objects. 12405 if (mEllipsize != where) { 12406 mEllipsize = where; 12407 12408 if (mLayout != null) { 12409 nullLayouts(); 12410 requestLayout(); 12411 invalidate(); 12412 } 12413 } 12414 } 12415 12416 /** 12417 * Sets how many times to repeat the marquee animation. Only applied if the 12418 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 12419 * 12420 * @see #getMarqueeRepeatLimit() 12421 * 12422 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 12423 */ 12424 public void setMarqueeRepeatLimit(int marqueeLimit) { 12425 mMarqueeRepeatLimit = marqueeLimit; 12426 } 12427 12428 /** 12429 * Gets the number of times the marquee animation is repeated. Only meaningful if the 12430 * TextView has marquee enabled. 12431 * 12432 * @return the number of times the marquee animation is repeated. -1 if the animation 12433 * repeats indefinitely 12434 * 12435 * @see #setMarqueeRepeatLimit(int) 12436 * 12437 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 12438 */ 12439 @InspectableProperty 12440 public int getMarqueeRepeatLimit() { 12441 return mMarqueeRepeatLimit; 12442 } 12443 12444 /** 12445 * Returns where, if anywhere, words that are longer than the view 12446 * is wide should be ellipsized. 12447 */ 12448 @InspectableProperty 12449 @ViewDebug.ExportedProperty 12450 public TextUtils.TruncateAt getEllipsize() { 12451 return mEllipsize; 12452 } 12453 12454 /** 12455 * Set the TextView so that when it takes focus, all the text is 12456 * selected. 12457 * 12458 * @attr ref android.R.styleable#TextView_selectAllOnFocus 12459 */ 12460 @android.view.RemotableViewMethod 12461 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 12462 createEditorIfNeeded(); 12463 mEditor.mSelectAllOnFocus = selectAllOnFocus; 12464 12465 if (selectAllOnFocus && !(mText instanceof Spannable)) { 12466 setText(mText, BufferType.SPANNABLE); 12467 } 12468 } 12469 12470 /** 12471 * Set whether the cursor is visible. The default is true. Note that this property only 12472 * makes sense for editable TextView. If IME is consuming the input, the cursor will always be 12473 * invisible, visibility will be updated as the last state when IME does not consume 12474 * the input anymore. 12475 * 12476 * @see #isCursorVisible() 12477 * 12478 * @attr ref android.R.styleable#TextView_cursorVisible 12479 */ 12480 @android.view.RemotableViewMethod 12481 public void setCursorVisible(boolean visible) { 12482 mCursorVisibleFromAttr = visible; 12483 updateCursorVisibleInternal(); 12484 } 12485 12486 /** 12487 * Sets the IME is consuming the input and make the cursor invisible if {@code imeConsumesInput} 12488 * is {@code true}. Otherwise, make the cursor visible. 12489 * 12490 * @param imeConsumesInput {@code true} if IME is consuming the input 12491 * 12492 * @hide 12493 */ 12494 public void setImeConsumesInput(boolean imeConsumesInput) { 12495 mImeIsConsumingInput = imeConsumesInput; 12496 updateCursorVisibleInternal(); 12497 } 12498 12499 private void updateCursorVisibleInternal() { 12500 boolean visible = mCursorVisibleFromAttr && !mImeIsConsumingInput; 12501 if (visible && mEditor == null) return; // visible is the default value with no edit data 12502 createEditorIfNeeded(); 12503 if (mEditor.mCursorVisible != visible) { 12504 mEditor.mCursorVisible = visible; 12505 invalidate(); 12506 12507 mEditor.makeBlink(); 12508 12509 // InsertionPointCursorController depends on mCursorVisible 12510 mEditor.prepareCursorControllers(); 12511 } 12512 } 12513 12514 /** 12515 * @return whether or not the cursor is visible (assuming this TextView is editable). This 12516 * method may return {@code false} when the IME is consuming the input even if the 12517 * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)} 12518 * is called. 12519 * 12520 * @see #setCursorVisible(boolean) 12521 * 12522 * @attr ref android.R.styleable#TextView_cursorVisible 12523 */ 12524 @InspectableProperty 12525 public boolean isCursorVisible() { 12526 // true is the default value 12527 return mEditor == null ? true : mEditor.mCursorVisible; 12528 } 12529 12530 /** 12531 * @return whether cursor is visible without regard to {@code mImeIsConsumingInput}. 12532 * {@code true} is the default value. 12533 * 12534 * @see #setCursorVisible(boolean) 12535 * @hide 12536 */ 12537 public boolean isCursorVisibleFromAttr() { 12538 return mCursorVisibleFromAttr; 12539 } 12540 12541 private boolean canMarquee() { 12542 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 12543 return width > 0 && (mLayout.getLineWidth(0) > width 12544 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null 12545 && mSavedMarqueeModeLayout.getLineWidth(0) > width)); 12546 } 12547 12548 /** 12549 * @hide 12550 */ 12551 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 12552 protected void startMarquee() { 12553 // Do not ellipsize EditText 12554 if (getKeyListener() != null) return; 12555 12556 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 12557 return; 12558 } 12559 12560 if ((mMarquee == null || mMarquee.isStopped()) && isAggregatedVisible() 12561 && (isFocused() || isSelected()) && getLineCount() == 1 && canMarquee()) { 12562 12563 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 12564 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 12565 final Layout tmp = mLayout; 12566 mLayout = mSavedMarqueeModeLayout; 12567 mSavedMarqueeModeLayout = tmp; 12568 setHorizontalFadingEdgeEnabled(true); 12569 requestLayout(); 12570 invalidate(); 12571 } 12572 12573 if (mMarquee == null) mMarquee = new Marquee(this); 12574 mMarquee.start(mMarqueeRepeatLimit); 12575 } 12576 } 12577 12578 /** 12579 * @hide 12580 */ 12581 protected void stopMarquee() { 12582 if (mMarquee != null && !mMarquee.isStopped()) { 12583 mMarquee.stop(); 12584 } 12585 12586 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 12587 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 12588 final Layout tmp = mSavedMarqueeModeLayout; 12589 mSavedMarqueeModeLayout = mLayout; 12590 mLayout = tmp; 12591 setHorizontalFadingEdgeEnabled(false); 12592 requestLayout(); 12593 invalidate(); 12594 } 12595 } 12596 12597 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 12598 private void startStopMarquee(boolean start) { 12599 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 12600 if (start) { 12601 startMarquee(); 12602 } else { 12603 stopMarquee(); 12604 } 12605 } 12606 } 12607 12608 /** 12609 * This method is called when the text is changed, in case any subclasses 12610 * would like to know. 12611 * 12612 * Within <code>text</code>, the <code>lengthAfter</code> characters 12613 * beginning at <code>start</code> have just replaced old text that had 12614 * length <code>lengthBefore</code>. It is an error to attempt to make 12615 * changes to <code>text</code> from this callback. 12616 * 12617 * @param text The text the TextView is displaying 12618 * @param start The offset of the start of the range of the text that was 12619 * modified 12620 * @param lengthBefore The length of the former text that has been replaced 12621 * @param lengthAfter The length of the replacement modified text 12622 */ 12623 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 12624 // intentionally empty, template pattern method can be overridden by subclasses 12625 } 12626 12627 /** 12628 * This method is called when the selection has changed, in case any 12629 * subclasses would like to know. 12630 * </p> 12631 * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs 12632 * the accessibility subsystem about the selection change. 12633 * </p> 12634 * 12635 * @param selStart The new selection start location. 12636 * @param selEnd The new selection end location. 12637 */ 12638 @CallSuper 12639 protected void onSelectionChanged(int selStart, int selEnd) { 12640 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 12641 } 12642 12643 /** 12644 * Adds a TextWatcher to the list of those whose methods are called 12645 * whenever this TextView's text changes. 12646 * <p> 12647 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 12648 * not called after {@link #setText} calls. Now, doing {@link #setText} 12649 * if there are any text changed listeners forces the buffer type to 12650 * Editable if it would not otherwise be and does call this method. 12651 */ 12652 public void addTextChangedListener(TextWatcher watcher) { 12653 if (mListeners == null) { 12654 mListeners = new ArrayList<TextWatcher>(); 12655 } 12656 12657 mListeners.add(watcher); 12658 } 12659 12660 /** 12661 * Removes the specified TextWatcher from the list of those whose 12662 * methods are called 12663 * whenever this TextView's text changes. 12664 */ 12665 public void removeTextChangedListener(TextWatcher watcher) { 12666 if (mListeners != null) { 12667 int i = mListeners.indexOf(watcher); 12668 12669 if (i >= 0) { 12670 mListeners.remove(i); 12671 } 12672 } 12673 } 12674 12675 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 12676 if (mListeners != null) { 12677 final ArrayList<TextWatcher> list = mListeners; 12678 final int count = list.size(); 12679 for (int i = 0; i < count; i++) { 12680 list.get(i).beforeTextChanged(text, start, before, after); 12681 } 12682 } 12683 12684 // The spans that are inside or intersect the modified region no longer make sense 12685 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 12686 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 12687 } 12688 12689 // Removes all spans that are inside or actually overlap the start..end range 12690 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 12691 if (!(mText instanceof Editable)) return; 12692 Editable text = (Editable) mText; 12693 12694 T[] spans = text.getSpans(start, end, type); 12695 ArrayList<T> spansToRemove = new ArrayList<>(); 12696 for (T span : spans) { 12697 final int spanStart = text.getSpanStart(span); 12698 final int spanEnd = text.getSpanEnd(span); 12699 if (spanEnd == start || spanStart == end) continue; 12700 spansToRemove.add(span); 12701 } 12702 for (T span : spansToRemove) { 12703 text.removeSpan(span); 12704 } 12705 } 12706 12707 void removeAdjacentSuggestionSpans(final int pos) { 12708 if (!(mText instanceof Editable)) return; 12709 final Editable text = (Editable) mText; 12710 12711 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 12712 final int length = spans.length; 12713 for (int i = 0; i < length; i++) { 12714 final int spanStart = text.getSpanStart(spans[i]); 12715 final int spanEnd = text.getSpanEnd(spans[i]); 12716 if (spanEnd == pos || spanStart == pos) { 12717 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 12718 text.removeSpan(spans[i]); 12719 } 12720 } 12721 } 12722 } 12723 12724 /** 12725 * Not private so it can be called from an inner class without going 12726 * through a thunk. 12727 */ 12728 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 12729 if (mListeners != null) { 12730 final ArrayList<TextWatcher> list = mListeners; 12731 final int count = list.size(); 12732 for (int i = 0; i < count; i++) { 12733 list.get(i).onTextChanged(text, start, before, after); 12734 } 12735 } 12736 12737 if (mEditor != null) mEditor.sendOnTextChanged(start, before, after); 12738 } 12739 12740 /** 12741 * Not private so it can be called from an inner class without going 12742 * through a thunk. 12743 */ 12744 void sendAfterTextChanged(Editable text) { 12745 if (mListeners != null) { 12746 final ArrayList<TextWatcher> list = mListeners; 12747 final int count = list.size(); 12748 for (int i = 0; i < count; i++) { 12749 list.get(i).afterTextChanged(text); 12750 } 12751 } 12752 12753 notifyListeningManagersAfterTextChanged(); 12754 12755 hideErrorIfUnchanged(); 12756 } 12757 12758 /** 12759 * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are 12760 * interested on text changes. 12761 */ 12762 private void notifyListeningManagersAfterTextChanged() { 12763 12764 // Autofill 12765 if (isAutofillable()) { 12766 // It is important to not check whether the view is important for autofill 12767 // since the user can trigger autofill manually on not important views. 12768 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 12769 if (afm != null) { 12770 if (android.view.autofill.Helper.sVerbose) { 12771 Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged"); 12772 } 12773 afm.notifyValueChanged(TextView.this); 12774 } 12775 } 12776 12777 notifyContentCaptureTextChanged(); 12778 } 12779 12780 /** 12781 * Notifies the ContentCapture service that the text of the view has changed (only if 12782 * ContentCapture has been notified of this view's existence already). 12783 * 12784 * @hide 12785 */ 12786 public void notifyContentCaptureTextChanged() { 12787 // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead 12788 // of using isLaidout(), so it's not called in cases where it's laid out but a 12789 // notifyAppeared was not sent. 12790 if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) { 12791 final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class); 12792 if (cm != null && cm.isContentCaptureEnabled()) { 12793 final ContentCaptureSession session = getContentCaptureSession(); 12794 if (session != null) { 12795 // TODO(b/111276913): pass flags when edited by user / add CTS test 12796 session.notifyViewTextChanged(getAutofillId(), getText()); 12797 } 12798 } 12799 } 12800 } 12801 12802 private boolean isAutofillable() { 12803 // It is important to not check whether the view is important for autofill 12804 // since the user can trigger autofill manually on not important views. 12805 return getAutofillType() != AUTOFILL_TYPE_NONE; 12806 } 12807 12808 void updateAfterEdit() { 12809 invalidate(); 12810 int curs = getSelectionStart(); 12811 12812 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 12813 registerForPreDraw(); 12814 } 12815 12816 checkForResize(); 12817 12818 if (curs >= 0) { 12819 mHighlightPathBogus = true; 12820 if (mEditor != null) mEditor.makeBlink(); 12821 bringPointIntoView(curs); 12822 } 12823 } 12824 12825 /** 12826 * Not private so it can be called from an inner class without going 12827 * through a thunk. 12828 */ 12829 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 12830 sLastCutCopyOrTextChangedTime = 0; 12831 12832 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 12833 if (ims == null || ims.mBatchEditNesting == 0) { 12834 updateAfterEdit(); 12835 } 12836 if (ims != null) { 12837 ims.mContentChanged = true; 12838 if (ims.mChangedStart < 0) { 12839 ims.mChangedStart = start; 12840 ims.mChangedEnd = start + before; 12841 } else { 12842 ims.mChangedStart = Math.min(ims.mChangedStart, start); 12843 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 12844 } 12845 ims.mChangedDelta += after - before; 12846 } 12847 resetErrorChangedFlag(); 12848 sendOnTextChanged(buffer, start, before, after); 12849 onTextChanged(buffer, start, before, after); 12850 12851 mHideHint = false; 12852 clearGesturePreviewHighlight(); 12853 } 12854 12855 /** 12856 * Not private so it can be called from an inner class without going 12857 * through a thunk. 12858 */ 12859 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 12860 // XXX Make the start and end move together if this ends up 12861 // spending too much time invalidating. 12862 12863 boolean selChanged = false; 12864 int newSelStart = -1, newSelEnd = -1; 12865 12866 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 12867 12868 if (what == Selection.SELECTION_END) { 12869 selChanged = true; 12870 newSelEnd = newStart; 12871 12872 if (oldStart >= 0 || newStart >= 0) { 12873 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 12874 checkForResize(); 12875 registerForPreDraw(); 12876 if (mEditor != null) mEditor.makeBlink(); 12877 } 12878 } 12879 12880 if (what == Selection.SELECTION_START) { 12881 selChanged = true; 12882 newSelStart = newStart; 12883 12884 if (oldStart >= 0 || newStart >= 0) { 12885 int end = Selection.getSelectionEnd(buf); 12886 invalidateCursor(end, oldStart, newStart); 12887 } 12888 } 12889 12890 if (selChanged) { 12891 clearGesturePreviewHighlight(); 12892 mHighlightPathBogus = true; 12893 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 12894 12895 if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) { 12896 if (newSelStart < 0) { 12897 newSelStart = Selection.getSelectionStart(buf); 12898 } 12899 if (newSelEnd < 0) { 12900 newSelEnd = Selection.getSelectionEnd(buf); 12901 } 12902 12903 if (mEditor != null) { 12904 mEditor.refreshTextActionMode(); 12905 if (!hasSelection() 12906 && mEditor.getTextActionMode() == null && hasTransientState()) { 12907 // User generated selection has been removed. 12908 setHasTransientState(false); 12909 } 12910 } 12911 onSelectionChanged(newSelStart, newSelEnd); 12912 } 12913 } 12914 12915 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle 12916 || what instanceof CharacterStyle) { 12917 if (ims == null || ims.mBatchEditNesting == 0) { 12918 invalidate(); 12919 mHighlightPathBogus = true; 12920 checkForResize(); 12921 } else { 12922 ims.mContentChanged = true; 12923 } 12924 if (mEditor != null) { 12925 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 12926 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 12927 mEditor.invalidateHandlesAndActionMode(); 12928 } 12929 } 12930 12931 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 12932 mHighlightPathBogus = true; 12933 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 12934 ims.mSelectionModeChanged = true; 12935 } 12936 12937 if (Selection.getSelectionStart(buf) >= 0) { 12938 if (ims == null || ims.mBatchEditNesting == 0) { 12939 invalidateCursor(); 12940 } else { 12941 ims.mCursorChanged = true; 12942 } 12943 } 12944 } 12945 12946 if (what instanceof ParcelableSpan) { 12947 // If this is a span that can be sent to a remote process, 12948 // the current extract editor would be interested in it. 12949 if (ims != null && ims.mExtractedTextRequest != null) { 12950 if (ims.mBatchEditNesting != 0) { 12951 if (oldStart >= 0) { 12952 if (ims.mChangedStart > oldStart) { 12953 ims.mChangedStart = oldStart; 12954 } 12955 if (ims.mChangedStart > oldEnd) { 12956 ims.mChangedStart = oldEnd; 12957 } 12958 } 12959 if (newStart >= 0) { 12960 if (ims.mChangedStart > newStart) { 12961 ims.mChangedStart = newStart; 12962 } 12963 if (ims.mChangedStart > newEnd) { 12964 ims.mChangedStart = newEnd; 12965 } 12966 } 12967 } else { 12968 if (DEBUG_EXTRACT) { 12969 Log.v(LOG_TAG, "Span change outside of batch: " 12970 + oldStart + "-" + oldEnd + "," 12971 + newStart + "-" + newEnd + " " + what); 12972 } 12973 ims.mContentChanged = true; 12974 } 12975 } 12976 } 12977 12978 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 12979 && what instanceof SpellCheckSpan) { 12980 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 12981 } 12982 } 12983 12984 @Override 12985 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 12986 if (isTemporarilyDetached()) { 12987 // If we are temporarily in the detach state, then do nothing. 12988 super.onFocusChanged(focused, direction, previouslyFocusedRect); 12989 return; 12990 } 12991 12992 mHideHint = false; 12993 12994 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 12995 12996 if (focused) { 12997 if (mSpannable != null) { 12998 MetaKeyKeyListener.resetMetaState(mSpannable); 12999 } 13000 } 13001 13002 startStopMarquee(focused); 13003 13004 if (mTransformation != null) { 13005 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 13006 } 13007 13008 super.onFocusChanged(focused, direction, previouslyFocusedRect); 13009 } 13010 13011 @Override 13012 public void onWindowFocusChanged(boolean hasWindowFocus) { 13013 super.onWindowFocusChanged(hasWindowFocus); 13014 13015 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 13016 13017 startStopMarquee(hasWindowFocus); 13018 } 13019 13020 @Override 13021 protected void onVisibilityChanged(View changedView, int visibility) { 13022 super.onVisibilityChanged(changedView, visibility); 13023 if (mEditor != null && visibility != VISIBLE) { 13024 mEditor.hideCursorAndSpanControllers(); 13025 stopTextActionMode(); 13026 } 13027 } 13028 13029 @Override 13030 public void onVisibilityAggregated(boolean isVisible) { 13031 super.onVisibilityAggregated(isVisible); 13032 startStopMarquee(isVisible); 13033 } 13034 13035 /** 13036 * Use {@link BaseInputConnection#removeComposingSpans 13037 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 13038 * state from this text view. 13039 */ 13040 public void clearComposingText() { 13041 if (mText instanceof Spannable) { 13042 BaseInputConnection.removeComposingSpans(mSpannable); 13043 } 13044 } 13045 13046 @Override 13047 public void setSelected(boolean selected) { 13048 boolean wasSelected = isSelected(); 13049 13050 super.setSelected(selected); 13051 13052 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 13053 if (selected) { 13054 startMarquee(); 13055 } else { 13056 stopMarquee(); 13057 } 13058 } 13059 } 13060 13061 /** 13062 * Called from onTouchEvent() to prevent the touches by secondary fingers. 13063 * Dragging on handles can revise cursor/selection, so can dragging on the text view. 13064 * This method is a lock to avoid processing multiple fingers on both text view and handles. 13065 * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work. 13066 * 13067 * @param event The motion event that is being handled and carries the pointer info. 13068 * @param fromHandleView true if the event is delivered to selection handle or insertion 13069 * handle; false if this event is delivered to TextView. 13070 * @return Returns true to indicate that onTouchEvent() can continue processing the motion 13071 * event, otherwise false. 13072 * - Always returns true for the first finger. 13073 * - For secondary fingers, if the first or current finger is from TextView, returns false. 13074 * This is to make touch mutually exclusive between the TextView and the handles, but 13075 * not among the handles. 13076 */ 13077 boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) { 13078 boolean res = true; 13079 if (mPrimePointerId == NO_POINTER_ID) { 13080 mPrimePointerId = event.getPointerId(0); 13081 mIsPrimePointerFromHandleView = fromHandleView; 13082 } else if (mPrimePointerId != event.getPointerId(0)) { 13083 res = mIsPrimePointerFromHandleView && fromHandleView; 13084 } 13085 if (event.getActionMasked() == MotionEvent.ACTION_UP 13086 || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { 13087 mPrimePointerId = -1; 13088 } 13089 return res; 13090 } 13091 13092 @Override 13093 public boolean onTouchEvent(MotionEvent event) { 13094 if (DEBUG_CURSOR) { 13095 logCursor("onTouchEvent", "%d: %s (%f,%f)", 13096 event.getSequenceNumber(), 13097 MotionEvent.actionToString(event.getActionMasked()), 13098 event.getX(), event.getY()); 13099 } 13100 mLastInputSource = event.getSource(); 13101 final int action = event.getActionMasked(); 13102 if (mEditor != null) { 13103 if (!isFromPrimePointer(event, false)) { 13104 return true; 13105 } 13106 13107 mEditor.onTouchEvent(event); 13108 13109 if (mEditor.mInsertionPointCursorController != null 13110 && mEditor.mInsertionPointCursorController.isCursorBeingModified()) { 13111 return true; 13112 } 13113 if (mEditor.mSelectionModifierCursorController != null 13114 && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { 13115 return true; 13116 } 13117 } 13118 13119 final boolean superResult = super.onTouchEvent(event); 13120 if (DEBUG_CURSOR) { 13121 logCursor("onTouchEvent", "superResult=%s", superResult); 13122 } 13123 13124 /* 13125 * Don't handle the release after a long press, because it will move the selection away from 13126 * whatever the menu action was trying to affect. If the long press should have triggered an 13127 * insertion action mode, we can now actually show it. 13128 */ 13129 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 13130 mEditor.mDiscardNextActionUp = false; 13131 if (DEBUG_CURSOR) { 13132 logCursor("onTouchEvent", "release after long press detected"); 13133 } 13134 if (mEditor.mIsInsertionActionModeStartPending) { 13135 mEditor.startInsertionActionMode(); 13136 mEditor.mIsInsertionActionModeStartPending = false; 13137 } 13138 return superResult; 13139 } 13140 13141 // At this point, the event is not a long press, otherwise it would be handled above. 13142 if (Flags.handwritingEndOfLineTap() && action == MotionEvent.ACTION_UP 13143 && shouldStartHandwritingForEndOfLineTap(event)) { 13144 InputMethodManager imm = getInputMethodManager(); 13145 if (imm != null) { 13146 imm.startStylusHandwriting(this); 13147 return true; 13148 } 13149 } 13150 13151 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) 13152 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 13153 13154 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 13155 && mText instanceof Spannable && mLayout != null) { 13156 boolean handled = false; 13157 13158 if (mMovement != null) { 13159 handled |= mMovement.onTouchEvent(this, mSpannable, event); 13160 } 13161 13162 final boolean textIsSelectable = isTextSelectable(); 13163 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 13164 // The LinkMovementMethod which should handle taps on links has not been installed 13165 // on non editable text that support text selection. 13166 // We reproduce its behavior here to open links for these. 13167 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(), 13168 getSelectionEnd(), ClickableSpan.class); 13169 13170 if (links.length > 0) { 13171 links[0].onClick(this); 13172 handled = true; 13173 } 13174 } 13175 13176 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 13177 // Show the IME, except when selecting in read-only text. 13178 final InputMethodManager imm = getInputMethodManager(); 13179 viewClicked(imm); 13180 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null 13181 && !showAutofillDialog()) { 13182 imm.showSoftInput(this, 0); 13183 } 13184 13185 // The above condition ensures that the mEditor is not null 13186 mEditor.onTouchUpEvent(event); 13187 13188 handled = true; 13189 } 13190 13191 if (handled) { 13192 return true; 13193 } 13194 } 13195 13196 return superResult; 13197 } 13198 13199 /** 13200 * If handwriting is supported, the TextView is already focused and not empty, and the cursor is 13201 * at the end of a line, a stylus tap after the end of the line will trigger handwriting. 13202 */ 13203 private boolean shouldStartHandwritingForEndOfLineTap(MotionEvent actionUpEvent) { 13204 if (!onCheckIsTextEditor() 13205 || !isEnabled() 13206 || !isAutoHandwritingEnabled() 13207 || TextUtils.isEmpty(mText) 13208 || didTouchFocusSelect() 13209 || mLayout == null 13210 || !actionUpEvent.isStylusPointer()) { 13211 return false; 13212 } 13213 int cursorOffset = getSelectionStart(); 13214 if (cursorOffset < 0 || getSelectionEnd() != cursorOffset) { 13215 return false; 13216 } 13217 int cursorLine = mLayout.getLineForOffset(cursorOffset); 13218 int cursorLineEnd = mLayout.getLineEnd(cursorLine); 13219 if (cursorLine != mLayout.getLineCount() - 1) { 13220 cursorLineEnd--; 13221 } 13222 if (cursorLineEnd != cursorOffset) { 13223 return false; 13224 } 13225 // Check that the stylus down point is within the same line as the cursor. 13226 if (getLineAtCoordinate(actionUpEvent.getY()) != cursorLine) { 13227 return false; 13228 } 13229 // Check that the stylus down point is after the end of the line. 13230 float localX = convertToLocalHorizontalCoordinate(actionUpEvent.getX()); 13231 if (mLayout.getParagraphDirection(cursorLine) == Layout.DIR_RIGHT_TO_LEFT 13232 ? localX >= mLayout.getLineLeft(cursorLine) 13233 : localX <= mLayout.getLineRight(cursorLine)) { 13234 return false; 13235 } 13236 return isStylusHandwritingAvailable(); 13237 } 13238 13239 /** 13240 * Returns true when need to show UIs, e.g. floating toolbar, etc, for finger based interaction. 13241 * 13242 * @return true if UIs need to show for finger interaciton. false if UIs are not necessary. 13243 * @hide 13244 */ 13245 public final boolean showUIForTouchScreen() { 13246 return (mLastInputSource & InputDevice.SOURCE_TOUCHSCREEN) 13247 == InputDevice.SOURCE_TOUCHSCREEN; 13248 } 13249 13250 /** 13251 * The fill dialog UI is a more conspicuous and efficient interface than dropdown UI. 13252 * If autofill suggestions are available when the user clicks on a field that supports filling 13253 * the dialog UI, Autofill will pop up a fill dialog. The dialog will take up a larger area 13254 * to display the datasets, so it is easy for users to pay attention to the datasets and 13255 * selecting a dataset. The autofill dialog is shown as the bottom sheet, the better 13256 * experience is not to show the IME if there is a fill dialog. 13257 */ 13258 private boolean showAutofillDialog() { 13259 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 13260 if (afm != null) { 13261 return afm.showAutofillDialog(this); 13262 } 13263 return false; 13264 } 13265 13266 @Override 13267 public boolean onGenericMotionEvent(MotionEvent event) { 13268 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 13269 try { 13270 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) { 13271 return true; 13272 } 13273 } catch (AbstractMethodError ex) { 13274 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 13275 // Ignore its absence in case third party applications implemented the 13276 // interface directly. 13277 } 13278 } 13279 return super.onGenericMotionEvent(event); 13280 } 13281 13282 @Override 13283 protected void onCreateContextMenu(ContextMenu menu) { 13284 if (mEditor != null) { 13285 mEditor.onCreateContextMenu(menu); 13286 } 13287 } 13288 13289 @Override 13290 public boolean showContextMenu() { 13291 if (mEditor != null) { 13292 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); 13293 } 13294 return super.showContextMenu(); 13295 } 13296 13297 @Override 13298 public boolean showContextMenu(float x, float y) { 13299 if (mEditor != null) { 13300 mEditor.setContextMenuAnchor(x, y); 13301 } 13302 return super.showContextMenu(x, y); 13303 } 13304 13305 /** 13306 * @return True iff this TextView contains a text that can be edited, or if this is 13307 * a selectable TextView. 13308 */ 13309 @UnsupportedAppUsage 13310 boolean isTextEditable() { 13311 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 13312 } 13313 13314 /** 13315 * @return true if this TextView could be filled by an Autofill service. Note that disabled 13316 * fields can still be filled. 13317 */ 13318 @UnsupportedAppUsage 13319 boolean isTextAutofillable() { 13320 return mText instanceof Editable && onCheckIsTextEditor(); 13321 } 13322 13323 /** 13324 * Returns true, only while processing a touch gesture, if the initial 13325 * touch down event caused focus to move to the text view and as a result 13326 * its selection changed. Only valid while processing the touch gesture 13327 * of interest, in an editable text view. 13328 */ 13329 public boolean didTouchFocusSelect() { 13330 return mEditor != null && mEditor.mTouchFocusSelected; 13331 } 13332 13333 @Override 13334 public void cancelLongPress() { 13335 super.cancelLongPress(); 13336 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 13337 } 13338 13339 @Override 13340 public boolean onTrackballEvent(MotionEvent event) { 13341 if (mMovement != null && mSpannable != null && mLayout != null) { 13342 if (mMovement.onTrackballEvent(this, mSpannable, event)) { 13343 return true; 13344 } 13345 } 13346 13347 return super.onTrackballEvent(event); 13348 } 13349 13350 /** 13351 * Sets the Scroller used for producing a scrolling animation 13352 * 13353 * @param s A Scroller instance 13354 */ 13355 public void setScroller(Scroller s) { 13356 mScroller = s; 13357 } 13358 13359 @Override 13360 protected float getLeftFadingEdgeStrength() { 13361 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 13362 final Marquee marquee = mMarquee; 13363 if (marquee.shouldDrawLeftFade()) { 13364 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f); 13365 } else { 13366 return 0.0f; 13367 } 13368 } else if (getLineCount() == 1) { 13369 final float lineLeft = getLayout().getLineLeft(0); 13370 if (lineLeft > mScrollX) return 0.0f; 13371 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft); 13372 } 13373 return super.getLeftFadingEdgeStrength(); 13374 } 13375 13376 @Override 13377 protected float getRightFadingEdgeStrength() { 13378 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 13379 final Marquee marquee = mMarquee; 13380 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll()); 13381 } else if (getLineCount() == 1) { 13382 final float rightEdge = mScrollX + 13383 (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight()); 13384 final float lineRight = getLayout().getLineRight(0); 13385 if (lineRight < rightEdge) return 0.0f; 13386 return getHorizontalFadingEdgeStrength(rightEdge, lineRight); 13387 } 13388 return super.getRightFadingEdgeStrength(); 13389 } 13390 13391 /** 13392 * Calculates the fading edge strength as the ratio of the distance between two 13393 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute 13394 * value for the distance calculation. 13395 * 13396 * @param position1 A horizontal position. 13397 * @param position2 A horizontal position. 13398 * @return Fading edge strength between [0.0f, 1.0f]. 13399 */ 13400 @FloatRange(from = 0.0, to = 1.0) 13401 private float getHorizontalFadingEdgeStrength(float position1, float position2) { 13402 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength(); 13403 if (horizontalFadingEdgeLength == 0) return 0.0f; 13404 final float diff = Math.abs(position1 - position2); 13405 if (diff > horizontalFadingEdgeLength) return 1.0f; 13406 return diff / horizontalFadingEdgeLength; 13407 } 13408 13409 private boolean isMarqueeFadeEnabled() { 13410 return mEllipsize == TextUtils.TruncateAt.MARQUEE 13411 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 13412 } 13413 13414 @Override 13415 protected int computeHorizontalScrollRange() { 13416 if (mLayout != null) { 13417 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT 13418 ? (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 13419 } 13420 13421 return super.computeHorizontalScrollRange(); 13422 } 13423 13424 @Override 13425 protected int computeVerticalScrollRange() { 13426 if (mLayout != null) { 13427 return mLayout.getHeight(); 13428 } 13429 return super.computeVerticalScrollRange(); 13430 } 13431 13432 @Override 13433 protected int computeVerticalScrollExtent() { 13434 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 13435 } 13436 13437 @Override 13438 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 13439 super.findViewsWithText(outViews, searched, flags); 13440 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 13441 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 13442 String searchedLowerCase = searched.toString().toLowerCase(); 13443 String textLowerCase = mText.toString().toLowerCase(); 13444 if (textLowerCase.contains(searchedLowerCase)) { 13445 outViews.add(this); 13446 } 13447 } 13448 } 13449 13450 /** 13451 * Type of the text buffer that defines the characteristics of the text such as static, 13452 * styleable, or editable. 13453 */ 13454 public enum BufferType { 13455 NORMAL, SPANNABLE, EDITABLE 13456 } 13457 13458 /** 13459 * Returns the TextView_textColor attribute from the TypedArray, if set, or 13460 * the TextAppearance_textColor from the TextView_textAppearance attribute, 13461 * if TextView_textColor was not set directly. 13462 * 13463 * @removed 13464 */ 13465 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 13466 if (attrs == null) { 13467 // Preserve behavior prior to removal of this API. 13468 throw new NullPointerException(); 13469 } 13470 13471 // It's not safe to use this method from apps. The parameter 'attrs' 13472 // must have been obtained using the TextView filter array which is not 13473 // available to the SDK. As such, we grab a default TypedArray with the 13474 // right filter instead here. 13475 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 13476 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 13477 if (colors == null) { 13478 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 13479 if (ap != 0) { 13480 final TypedArray appearance = context.obtainStyledAttributes( 13481 ap, R.styleable.TextAppearance); 13482 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 13483 appearance.recycle(); 13484 } 13485 } 13486 a.recycle(); 13487 13488 return colors; 13489 } 13490 13491 /** 13492 * Returns the default color from the TextView_textColor attribute from the 13493 * AttributeSet, if set, or the default color from the 13494 * TextAppearance_textColor from the TextView_textAppearance attribute, if 13495 * TextView_textColor was not set directly. 13496 * 13497 * @removed 13498 */ 13499 public static int getTextColor(Context context, TypedArray attrs, int def) { 13500 final ColorStateList colors = getTextColors(context, attrs); 13501 if (colors == null) { 13502 return def; 13503 } else { 13504 return colors.getDefaultColor(); 13505 } 13506 } 13507 13508 @Override 13509 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 13510 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 13511 // Handle Ctrl-only shortcuts. 13512 switch (keyCode) { 13513 case KeyEvent.KEYCODE_A: 13514 if (canSelectText()) { 13515 return onTextContextMenuItem(ID_SELECT_ALL); 13516 } 13517 break; 13518 case KeyEvent.KEYCODE_Z: 13519 if (canUndo()) { 13520 return onTextContextMenuItem(ID_UNDO); 13521 } 13522 break; 13523 case KeyEvent.KEYCODE_X: 13524 if (canCut()) { 13525 return onTextContextMenuItem(ID_CUT); 13526 } 13527 break; 13528 case KeyEvent.KEYCODE_C: 13529 if (canCopy()) { 13530 return onTextContextMenuItem(ID_COPY); 13531 } 13532 break; 13533 case KeyEvent.KEYCODE_V: 13534 if (canPaste()) { 13535 return onTextContextMenuItem(ID_PASTE); 13536 } 13537 break; 13538 case KeyEvent.KEYCODE_Y: 13539 if (canRedo()) { 13540 return onTextContextMenuItem(ID_REDO); 13541 } 13542 break; 13543 } 13544 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { 13545 // Handle Ctrl-Shift shortcuts. 13546 switch (keyCode) { 13547 case KeyEvent.KEYCODE_Z: 13548 if (canRedo()) { 13549 return onTextContextMenuItem(ID_REDO); 13550 } 13551 break; 13552 case KeyEvent.KEYCODE_V: 13553 if (canPaste()) { 13554 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); 13555 } 13556 } 13557 } 13558 return super.onKeyShortcut(keyCode, event); 13559 } 13560 13561 /** 13562 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 13563 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 13564 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 13565 * sufficient. 13566 */ 13567 boolean canSelectText() { 13568 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 13569 } 13570 13571 /** 13572 * Test based on the <i>intrinsic</i> charateristics of the TextView. 13573 * The text must be spannable and the movement method must allow for arbitary selection. 13574 * 13575 * See also {@link #canSelectText()}. 13576 */ 13577 boolean textCanBeSelected() { 13578 // prepareCursorController() relies on this method. 13579 // If you change this condition, make sure prepareCursorController is called anywhere 13580 // the value of this condition might be changed. 13581 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 13582 return isTextEditable() 13583 || (isTextSelectable() && mText instanceof Spannable && isEnabled()); 13584 } 13585 13586 @UnsupportedAppUsage 13587 private Locale getTextServicesLocale(boolean allowNullLocale) { 13588 // Start fetching the text services locale asynchronously. 13589 updateTextServicesLocaleAsync(); 13590 // If !allowNullLocale and there is no cached text services locale, just return the default 13591 // locale. 13592 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 13593 : mCurrentSpellCheckerLocaleCache; 13594 } 13595 13596 /** 13597 * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in 13598 * this {@link TextView}. 13599 * 13600 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 13601 * other apps may need to set this so that the system can user right user's resources and 13602 * services such as input methods and spell checkers.</p> 13603 * 13604 * @param user {@link UserHandle} who is considered to be the owner of the text shown in this 13605 * {@link TextView}. {@code null} to reset {@link #mTextOperationUser}. 13606 * @hide 13607 */ 13608 @RequiresPermission(INTERACT_ACROSS_USERS_FULL) 13609 public final void setTextOperationUser(@Nullable UserHandle user) { 13610 if (Objects.equals(mTextOperationUser, user)) { 13611 return; 13612 } 13613 if (user != null && !Process.myUserHandle().equals(user)) { 13614 // Just for preventing people from accidentally using this hidden API without 13615 // the required permission. The same permission is also checked in the system server. 13616 if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL) 13617 != PackageManager.PERMISSION_GRANTED) { 13618 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required." 13619 + " userId=" + user.getIdentifier() 13620 + " callingUserId" + UserHandle.myUserId()); 13621 } 13622 } 13623 mTextOperationUser = user; 13624 // Invalidate some resources 13625 mCurrentSpellCheckerLocaleCache = null; 13626 if (mEditor != null) { 13627 mEditor.onTextOperationUserChanged(); 13628 } 13629 } 13630 13631 @Override 13632 public boolean isAutoHandwritingEnabled() { 13633 return super.isAutoHandwritingEnabled() && !isAnyPasswordInputType(); 13634 } 13635 13636 /** @hide */ 13637 @Override 13638 public boolean shouldTrackHandwritingArea() { 13639 // The handwriting initiator tracks all editable TextViews regardless of whether handwriting 13640 // is supported, so that it can show an error message for unsupported editable TextViews. 13641 return super.shouldTrackHandwritingArea() 13642 || (Flags.handwritingUnsupportedMessage() && onCheckIsTextEditor()); 13643 } 13644 13645 /** @hide */ 13646 @Override 13647 public boolean isStylusHandwritingAvailable() { 13648 if (mTextOperationUser == null) { 13649 return super.isStylusHandwritingAvailable(); 13650 } 13651 final InputMethodManager imm = getInputMethodManager(); 13652 return imm.isStylusHandwritingAvailableAsUser(mTextOperationUser); 13653 } 13654 13655 @Nullable 13656 final TextServicesManager getTextServicesManagerForUser() { 13657 return getServiceManagerForUser("android", TextServicesManager.class); 13658 } 13659 13660 @Nullable 13661 final ClipboardManager getClipboardManagerForUser() { 13662 return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class); 13663 } 13664 13665 @Nullable 13666 final TextClassificationManager getTextClassificationManagerForUser() { 13667 return getServiceManagerForUser( 13668 getContext().getPackageName(), TextClassificationManager.class); 13669 } 13670 13671 @Nullable 13672 final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) { 13673 if (mTextOperationUser == null) { 13674 return getContext().getSystemService(managerClazz); 13675 } 13676 try { 13677 Context context = getContext().createPackageContextAsUser( 13678 packageName, 0 /* flags */, mTextOperationUser); 13679 return context.getSystemService(managerClazz); 13680 } catch (PackageManager.NameNotFoundException e) { 13681 return null; 13682 } 13683 } 13684 13685 /** 13686 * Starts {@link Activity} as a text-operation user if it is specified with 13687 * {@link #setTextOperationUser(UserHandle)}. 13688 * 13689 * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p> 13690 * 13691 * @param intent The description of the activity to start. 13692 */ 13693 void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) { 13694 if (mTextOperationUser != null) { 13695 getContext().startActivityAsUser(intent, mTextOperationUser); 13696 } else { 13697 getContext().startActivity(intent); 13698 } 13699 } 13700 13701 /** 13702 * This is a temporary method. Future versions may support multi-locale text. 13703 * Caveat: This method may not return the latest text services locale, but this should be 13704 * acceptable and it's more important to make this method asynchronous. 13705 * 13706 * @return The locale that should be used for a word iterator 13707 * in this TextView, based on the current spell checker settings, 13708 * the current IME's locale, or the system default locale. 13709 * Please note that a word iterator in this TextView is different from another word iterator 13710 * used by SpellChecker.java of TextView. This method should be used for the former. 13711 * @hide 13712 */ 13713 // TODO: Support multi-locale 13714 // TODO: Update the text services locale immediately after the keyboard locale is switched 13715 // by catching intent of keyboard switch event 13716 public Locale getTextServicesLocale() { 13717 return getTextServicesLocale(false /* allowNullLocale */); 13718 } 13719 13720 /** 13721 * @return {@code true} if this TextView is specialized for showing and interacting with the 13722 * extracted text in a full-screen input method. 13723 * @hide 13724 */ 13725 public boolean isInExtractedMode() { 13726 return false; 13727 } 13728 13729 /** 13730 * @return {@code true} if this widget supports auto-sizing text and has been configured to 13731 * auto-size. 13732 */ 13733 private boolean isAutoSizeEnabled() { 13734 return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE; 13735 } 13736 13737 /** 13738 * @return {@code true} if this TextView supports auto-sizing text to fit within its container. 13739 * @hide 13740 */ 13741 protected boolean supportsAutoSizeText() { 13742 return true; 13743 } 13744 13745 /** 13746 * This is a temporary method. Future versions may support multi-locale text. 13747 * Caveat: This method may not return the latest spell checker locale, but this should be 13748 * acceptable and it's more important to make this method asynchronous. 13749 * 13750 * @return The locale that should be used for a spell checker in this TextView, 13751 * based on the current spell checker settings, the current IME's locale, or the system default 13752 * locale. 13753 * @hide 13754 */ 13755 public Locale getSpellCheckerLocale() { 13756 return getTextServicesLocale(true /* allowNullLocale */); 13757 } 13758 13759 private void updateTextServicesLocaleAsync() { 13760 // AsyncTask.execute() uses a serial executor which means we don't have 13761 // to lock around updateTextServicesLocaleLocked() to prevent it from 13762 // being executed n times in parallel. 13763 AsyncTask.execute(new Runnable() { 13764 @Override 13765 public void run() { 13766 updateTextServicesLocaleLocked(); 13767 } 13768 }); 13769 } 13770 13771 @UnsupportedAppUsage 13772 private void updateTextServicesLocaleLocked() { 13773 final TextServicesManager textServicesManager = getTextServicesManagerForUser(); 13774 if (textServicesManager == null) { 13775 return; 13776 } 13777 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 13778 final Locale locale; 13779 if (subtype != null) { 13780 locale = subtype.getLocaleObject(); 13781 } else { 13782 locale = null; 13783 } 13784 mCurrentSpellCheckerLocaleCache = locale; 13785 } 13786 13787 void onLocaleChanged() { 13788 mEditor.onLocaleChanged(); 13789 } 13790 13791 /** 13792 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 13793 * Made available to achieve a consistent behavior. 13794 * @hide 13795 */ 13796 public WordIterator getWordIterator() { 13797 if (mEditor != null) { 13798 return mEditor.getWordIterator(); 13799 } else { 13800 return null; 13801 } 13802 } 13803 13804 /** @hide */ 13805 @Override 13806 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 13807 super.onPopulateAccessibilityEventInternal(event); 13808 13809 if (this.isAccessibilityDataSensitive() && !event.isAccessibilityDataSensitive()) { 13810 // This view's accessibility data is sensitive, but another view that generated this 13811 // event is not, so don't append this view's text to the event in order to prevent 13812 // sharing this view's contents with non-accessibility-tool services. 13813 return; 13814 } 13815 13816 final CharSequence text = getTextForAccessibility(); 13817 if (!TextUtils.isEmpty(text)) { 13818 event.getText().add(text); 13819 } 13820 } 13821 13822 @Override 13823 public CharSequence getAccessibilityClassName() { 13824 return TextView.class.getName(); 13825 } 13826 13827 /** @hide */ 13828 @Override 13829 protected void onProvideStructure(@NonNull ViewStructure structure, 13830 @ViewStructureType int viewFor, int flags) { 13831 super.onProvideStructure(structure, viewFor, flags); 13832 13833 final boolean isPassword = hasPasswordTransformationMethod() 13834 || isPasswordInputType(getInputType()); 13835 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 13836 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 13837 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 13838 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId); 13839 } 13840 if (mTextId != Resources.ID_NULL) { 13841 try { 13842 structure.setTextIdEntry(getResources().getResourceEntryName(mTextId)); 13843 } catch (Resources.NotFoundException e) { 13844 if (android.view.autofill.Helper.sVerbose) { 13845 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id " 13846 + mTextId + ": " + e.getMessage()); 13847 } 13848 } 13849 } 13850 String[] mimeTypes = getReceiveContentMimeTypes(); 13851 if (mimeTypes == null && mEditor != null) { 13852 // If the app hasn't set a listener for receiving content on this view (ie, 13853 // getReceiveContentMimeTypes() returns null), check if it implements the 13854 // keyboard image API and, if possible, use those MIME types as fallback. 13855 // This fallback is only in place for autofill, not other mechanisms for 13856 // inserting content. See AUTOFILL_NON_TEXT_REQUIRES_ON_RECEIVE_CONTENT_LISTENER 13857 // in TextViewOnReceiveContentListener for more info. 13858 mimeTypes = mEditor.getDefaultOnReceiveContentListener() 13859 .getFallbackMimeTypesForAutofill(this); 13860 } 13861 structure.setReceiveContentMimeTypes(mimeTypes); 13862 } 13863 13864 if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 13865 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 13866 if (mLayout == null) { 13867 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 13868 Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()"); 13869 } 13870 assumeLayout(); 13871 } 13872 Layout layout = mLayout; 13873 final int lineCount = layout.getLineCount(); 13874 if (lineCount <= 1) { 13875 // Simple case: this is a single line. 13876 final CharSequence text = getText(); 13877 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 13878 structure.setText(text); 13879 } else { 13880 structure.setText(text, getSelectionStart(), getSelectionEnd()); 13881 } 13882 } else { 13883 // Complex case: multi-line, could be scrolled or within a scroll container 13884 // so some lines are not visible. 13885 final int[] tmpCords = new int[2]; 13886 getLocationInWindow(tmpCords); 13887 final int topWindowLocation = tmpCords[1]; 13888 View root = this; 13889 ViewParent viewParent = getParent(); 13890 while (viewParent instanceof View) { 13891 root = (View) viewParent; 13892 viewParent = root.getParent(); 13893 } 13894 final int windowHeight = root.getHeight(); 13895 final int topLine; 13896 final int bottomLine; 13897 if (topWindowLocation >= 0) { 13898 // The top of the view is fully within its window; start text at line 0. 13899 topLine = getLineAtCoordinateUnclamped(0); 13900 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1); 13901 } else { 13902 // The top of hte window has scrolled off the top of the window; figure out 13903 // the starting line for this. 13904 topLine = getLineAtCoordinateUnclamped(-topWindowLocation); 13905 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation); 13906 } 13907 // We want to return some contextual lines above/below the lines that are 13908 // actually visible. 13909 int expandedTopLine = topLine - (bottomLine - topLine) / 2; 13910 if (expandedTopLine < 0) { 13911 expandedTopLine = 0; 13912 } 13913 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2; 13914 if (expandedBottomLine >= lineCount) { 13915 expandedBottomLine = lineCount - 1; 13916 } 13917 13918 // Convert lines into character offsets. 13919 int expandedTopChar = transformedToOriginal( 13920 layout.getLineStart(expandedTopLine), 13921 OffsetMapping.MAP_STRATEGY_CHARACTER); 13922 int expandedBottomChar = transformedToOriginal( 13923 layout.getLineEnd(expandedBottomLine), 13924 OffsetMapping.MAP_STRATEGY_CHARACTER); 13925 13926 // Take into account selection -- if there is a selection, we need to expand 13927 // the text we are returning to include that selection. 13928 final int selStart = getSelectionStart(); 13929 final int selEnd = getSelectionEnd(); 13930 if (selStart < selEnd) { 13931 if (selStart < expandedTopChar) { 13932 expandedTopChar = selStart; 13933 } 13934 if (selEnd > expandedBottomChar) { 13935 expandedBottomChar = selEnd; 13936 } 13937 } 13938 13939 // Get the text and trim it to the range we are reporting. 13940 CharSequence text = getText(); 13941 13942 if (text != null) { 13943 if (expandedTopChar > 0 || expandedBottomChar < text.length()) { 13944 // Cap the offsets to avoid an OOB exception. That can happen if the 13945 // displayed/layout text, on which these offsets are calculated, is longer 13946 // than the original text (such as when the view is translated by the 13947 // platform intelligence). 13948 // TODO(b/196433694): Figure out how to better handle the offset 13949 // calculations for this case (so we don't unnecessarily cutoff the original 13950 // text, for example). 13951 expandedTopChar = Math.min(expandedTopChar, text.length()); 13952 expandedBottomChar = Math.min(expandedBottomChar, text.length()); 13953 text = text.subSequence(expandedTopChar, expandedBottomChar); 13954 } 13955 13956 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 13957 structure.setText(text); 13958 } else { 13959 structure.setText(text, 13960 selStart - expandedTopChar, 13961 selEnd - expandedTopChar); 13962 13963 final int[] lineOffsets = new int[bottomLine - topLine + 1]; 13964 final int[] lineBaselines = new int[bottomLine - topLine + 1]; 13965 final int baselineOffset = getBaselineOffset(); 13966 for (int i = topLine; i <= bottomLine; i++) { 13967 lineOffsets[i - topLine] = transformedToOriginal(layout.getLineStart(i), 13968 OffsetMapping.MAP_STRATEGY_CHARACTER); 13969 lineBaselines[i - topLine] = 13970 layout.getLineBaseline(i) + baselineOffset; 13971 } 13972 structure.setTextLines(lineOffsets, lineBaselines); 13973 } 13974 } 13975 } 13976 13977 if (viewFor == VIEW_STRUCTURE_FOR_ASSIST 13978 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 13979 // Extract style information that applies to the TextView as a whole. 13980 int style = 0; 13981 int typefaceStyle = getTypefaceStyle(); 13982 if ((typefaceStyle & Typeface.BOLD) != 0) { 13983 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 13984 } 13985 if ((typefaceStyle & Typeface.ITALIC) != 0) { 13986 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC; 13987 } 13988 13989 // Global styles can also be set via TextView.setPaintFlags(). 13990 int paintFlags = mTextPaint.getFlags(); 13991 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) { 13992 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 13993 } 13994 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) { 13995 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE; 13996 } 13997 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { 13998 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU; 13999 } 14000 14001 // TextView does not have its own text background color. A background is either part 14002 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. 14003 structure.setTextStyle(getTextSize(), getCurrentTextColor(), 14004 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); 14005 } 14006 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 14007 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 14008 structure.setMinTextEms(getMinEms()); 14009 structure.setMaxTextEms(getMaxEms()); 14010 int maxLength = -1; 14011 for (InputFilter filter: getFilters()) { 14012 if (filter instanceof InputFilter.LengthFilter) { 14013 maxLength = ((InputFilter.LengthFilter) filter).getMax(); 14014 break; 14015 } 14016 } 14017 structure.setMaxTextLength(maxLength); 14018 } 14019 } 14020 if (mHintId != Resources.ID_NULL) { 14021 try { 14022 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId)); 14023 } catch (Resources.NotFoundException e) { 14024 if (android.view.autofill.Helper.sVerbose) { 14025 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id " 14026 + mHintId + ": " + e.getMessage()); 14027 } 14028 } 14029 } 14030 structure.setHint(getHint()); 14031 structure.setInputType(getInputType()); 14032 } 14033 14034 boolean canRequestAutofill() { 14035 if (!isAutofillable()) { 14036 return false; 14037 } 14038 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 14039 if (afm != null) { 14040 return afm.isEnabled(); 14041 } 14042 return false; 14043 } 14044 14045 private void requestAutofill() { 14046 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 14047 if (afm != null) { 14048 afm.requestAutofill(this); 14049 } 14050 } 14051 14052 @Override 14053 public void autofill(AutofillValue value) { 14054 if (android.view.autofill.Helper.sVerbose) { 14055 Log.v(LOG_TAG, "autofill() called on textview for id:" + getAutofillId()); 14056 } 14057 if (!isTextAutofillable()) { 14058 Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this); 14059 return; 14060 } 14061 if (!value.isText()) { 14062 Log.w(LOG_TAG, "value of type " + value.describeContents() 14063 + " cannot be autofilled into " + this); 14064 return; 14065 } 14066 final ClipData clip = ClipData.newPlainText("", value.getTextValue()); 14067 final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build(); 14068 performReceiveContent(payload); 14069 } 14070 14071 @Override 14072 public @AutofillType int getAutofillType() { 14073 return isTextAutofillable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; 14074 } 14075 14076 /** 14077 * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K 14078 * {@code char}s if longer. 14079 * 14080 * @return current text, {@code null} if the text is not editable 14081 * 14082 * @see View#getAutofillValue() 14083 */ 14084 @Override 14085 @Nullable 14086 public AutofillValue getAutofillValue() { 14087 if (isTextAutofillable()) { 14088 final CharSequence text = TextUtils.trimToParcelableSize(getText()); 14089 return AutofillValue.forText(text); 14090 } 14091 return null; 14092 } 14093 14094 /** @hide */ 14095 @Override 14096 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 14097 super.onInitializeAccessibilityEventInternal(event); 14098 14099 final boolean isPassword = hasPasswordTransformationMethod(); 14100 event.setPassword(isPassword); 14101 14102 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 14103 event.setFromIndex(Selection.getSelectionStart(mText)); 14104 event.setToIndex(Selection.getSelectionEnd(mText)); 14105 event.setItemCount(mText.length()); 14106 } 14107 } 14108 14109 /** @hide */ 14110 @Override 14111 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 14112 super.onInitializeAccessibilityNodeInfoInternal(info); 14113 14114 final boolean isPassword = hasPasswordTransformationMethod(); 14115 info.setPassword(isPassword); 14116 info.setText(getTextForAccessibility()); 14117 info.setHintText(mHint); 14118 info.setShowingHintText(isShowingHint()); 14119 14120 if (mBufferType == BufferType.EDITABLE) { 14121 info.setEditable(true); 14122 if (isEnabled()) { 14123 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT); 14124 } 14125 } 14126 14127 if (mEditor != null) { 14128 info.setInputType(mEditor.mInputType); 14129 14130 if (mEditor.mError != null) { 14131 info.setContentInvalid(true); 14132 info.setError(mEditor.mError); 14133 } 14134 // TextView will expose this action if it is editable and has focus. 14135 if (isTextEditable() && isFocused()) { 14136 CharSequence imeActionLabel = mContext.getResources().getString( 14137 com.android.internal.R.string.keyboardview_keycode_enter); 14138 if (getImeActionLabel() != null) { 14139 imeActionLabel = getImeActionLabel(); 14140 } 14141 AccessibilityNodeInfo.AccessibilityAction action = 14142 new AccessibilityNodeInfo.AccessibilityAction( 14143 R.id.accessibilityActionImeEnter, imeActionLabel); 14144 info.addAction(action); 14145 } 14146 } 14147 14148 if (!TextUtils.isEmpty(mText)) { 14149 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 14150 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 14151 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 14152 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 14153 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 14154 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 14155 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 14156 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 14157 info.setAvailableExtraData(Arrays.asList( 14158 EXTRA_DATA_RENDERING_INFO_KEY, 14159 EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY 14160 )); 14161 info.setTextSelectable(isTextSelectable() || isTextEditable()); 14162 } else { 14163 info.setAvailableExtraData(Arrays.asList( 14164 EXTRA_DATA_RENDERING_INFO_KEY 14165 )); 14166 } 14167 14168 if (isFocused()) { 14169 if (canCopy()) { 14170 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 14171 } 14172 if (canPaste()) { 14173 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 14174 } 14175 if (canCut()) { 14176 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 14177 } 14178 if (canReplace()) { 14179 info.addAction( 14180 AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TEXT_SUGGESTIONS); 14181 } 14182 if (canShare()) { 14183 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 14184 ACCESSIBILITY_ACTION_SHARE, 14185 getResources().getString(com.android.internal.R.string.share))); 14186 } 14187 if (canProcessText()) { // also implies mEditor is not null. 14188 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); 14189 mEditor.onInitializeSmartActionsAccessibilityNodeInfo(info); 14190 } 14191 } 14192 14193 // Check for known input filter types. 14194 final int numFilters = mFilters.length; 14195 for (int i = 0; i < numFilters; i++) { 14196 final InputFilter filter = mFilters[i]; 14197 if (filter instanceof InputFilter.LengthFilter) { 14198 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 14199 } 14200 } 14201 14202 if (!isSingleLine()) { 14203 info.setMultiLine(true); 14204 } 14205 14206 // A view should not be exposed as clickable/long-clickable to a service because of a 14207 // LinkMovementMethod or because it has selectable and non-editable text. 14208 if ((info.isClickable() || info.isLongClickable()) 14209 && (mMovement instanceof LinkMovementMethod 14210 || (isTextSelectable() && !isTextEditable()))) { 14211 if (!hasOnClickListeners()) { 14212 info.setClickable(false); 14213 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); 14214 } 14215 if (!hasOnLongClickListeners()) { 14216 info.setLongClickable(false); 14217 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); 14218 } 14219 } 14220 } 14221 14222 @Override 14223 public void addExtraDataToAccessibilityNodeInfo( 14224 AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { 14225 if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) { 14226 int positionInfoStartIndex = arguments.getInt( 14227 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); 14228 int positionInfoLength = arguments.getInt( 14229 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1); 14230 if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0) 14231 || (positionInfoStartIndex >= mText.length())) { 14232 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations"); 14233 return; 14234 } 14235 RectF[] boundingRects = new RectF[positionInfoLength]; 14236 final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); 14237 populateCharacterBounds(builder, positionInfoStartIndex, 14238 Math.min(positionInfoStartIndex + positionInfoLength, length()), 14239 viewportToContentHorizontalOffset(), viewportToContentVerticalOffset()); 14240 CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build(); 14241 for (int i = 0; i < positionInfoLength; i++) { 14242 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i); 14243 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) { 14244 RectF bounds = cursorAnchorInfo 14245 .getCharacterBounds(positionInfoStartIndex + i); 14246 if (bounds != null) { 14247 mapRectFromViewToScreenCoords(bounds, true); 14248 boundingRects[i] = bounds; 14249 } 14250 } 14251 } 14252 info.getExtras().putParcelableArray(extraDataKey, boundingRects); 14253 return; 14254 } 14255 if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) { 14256 final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo = 14257 AccessibilityNodeInfo.ExtraRenderingInfo.obtain(); 14258 extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height); 14259 extraRenderingInfo.setTextSizeInPx(getTextSize()); 14260 extraRenderingInfo.setTextSizeUnit(getTextSizeUnit()); 14261 info.setExtraRenderingInfo(extraRenderingInfo); 14262 } 14263 } 14264 14265 /** 14266 * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates. 14267 * This method obtains the view's visible rectangle whereas the method 14268 * {@link #getContentVisibleRect} returns the text layout's visible rectangle. 14269 * 14270 * @return true if at least part of the text content is visible; false if the text content is 14271 * completely clipped or translated out of the visible area. 14272 */ 14273 private boolean getViewVisibleRect(Rect rect) { 14274 if (!getLocalVisibleRect(rect)) { 14275 return false; 14276 } 14277 // getLocalVisibleRect returns a rect relative to the unscrolled left top corner of the 14278 // view. In other words, the returned rectangle's origin point is (-scrollX, -scrollY) in 14279 // view's coordinates. So we need to offset it with the negative scrolled amount to convert 14280 // it to view's coordinate. 14281 rect.offset(-getScrollX(), -getScrollY()); 14282 return true; 14283 } 14284 14285 /** 14286 * Helper method to set {@code rect} to the text content's non-clipped area in the view's 14287 * coordinates. 14288 * 14289 * @return true if at least part of the text content is visible; false if the text content is 14290 * completely clipped or translated out of the visible area. 14291 */ 14292 private boolean getContentVisibleRect(Rect rect) { 14293 if (!getViewVisibleRect(rect)) { 14294 return false; 14295 } 14296 // Clip the view's visible rect with the text layout's visible rect. 14297 return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(), 14298 getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom()); 14299 } 14300 14301 /** 14302 * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} 14303 * 14304 * @param builder The builder to populate 14305 * @param startIndex The starting character index to populate 14306 * @param endIndex The ending character index to populate 14307 * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the 14308 * content 14309 * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content 14310 * @hide 14311 */ 14312 public void populateCharacterBounds(CursorAnchorInfo.Builder builder, 14313 int startIndex, int endIndex, float viewportToContentHorizontalOffset, 14314 float viewportToContentVerticalOffset) { 14315 if (isOffsetMappingAvailable()) { 14316 // The text is transformed, and has different length, we don't support 14317 // character bounds in this case yet. 14318 return; 14319 } 14320 final Rect rect = new Rect(); 14321 getContentVisibleRect(rect); 14322 final RectF visibleRect = new RectF(rect); 14323 14324 final float[] characterBounds = getCharacterBounds(startIndex, endIndex, 14325 viewportToContentHorizontalOffset, viewportToContentVerticalOffset); 14326 final int limit = endIndex - startIndex; 14327 for (int offset = 0; offset < limit; ++offset) { 14328 final float left = characterBounds[offset * 4]; 14329 final float top = characterBounds[offset * 4 + 1]; 14330 final float right = characterBounds[offset * 4 + 2]; 14331 final float bottom = characterBounds[offset * 4 + 3]; 14332 14333 final boolean hasVisibleRegion = visibleRect.intersects(left, top, right, bottom); 14334 final boolean hasInVisibleRegion = !visibleRect.contains(left, top, right, bottom); 14335 int characterBoundsFlags = 0; 14336 if (hasVisibleRegion) { 14337 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; 14338 } 14339 if (hasInVisibleRegion) { 14340 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 14341 } 14342 14343 if (mLayout.isRtlCharAt(offset)) { 14344 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; 14345 } 14346 builder.addCharacterBounds(offset + startIndex, left, top, right, bottom, 14347 characterBoundsFlags); 14348 } 14349 } 14350 14351 /** 14352 * Return the bounds of the characters in the given range, in TextView's coordinates. 14353 * 14354 * @param start the start index of the interested text range, inclusive. 14355 * @param end the end index of the interested text range, exclusive. 14356 * @param layoutLeft the left of the given {@code layout} in the editor view's coordinates. 14357 * @param layoutTop the top of the given {@code layout} in the editor view's coordinates. 14358 * @return the character bounds stored in a flattened array, in the editor view's coordinates. 14359 */ 14360 private float[] getCharacterBounds(int start, int end, float layoutLeft, float layoutTop) { 14361 final float[] characterBounds = new float[4 * (end - start)]; 14362 mLayout.fillCharacterBounds(start, end, characterBounds, 0); 14363 for (int offset = 0; offset < end - start; ++offset) { 14364 characterBounds[4 * offset] += layoutLeft; 14365 characterBounds[4 * offset + 1] += layoutTop; 14366 characterBounds[4 * offset + 2] += layoutLeft; 14367 characterBounds[4 * offset + 3] += layoutTop; 14368 } 14369 return characterBounds; 14370 } 14371 14372 /** 14373 * Compute {@link CursorAnchorInfo} from this {@link TextView}. 14374 * 14375 * @param filter the {@link CursorAnchorInfo} update filter which specified the needed 14376 * information from IME. 14377 * @param cursorAnchorInfoBuilder a cached {@link CursorAnchorInfo.Builder} object used to build 14378 * the result {@link CursorAnchorInfo}. 14379 * @param viewToScreenMatrix a cached {@link Matrix} object used to compute the view to screen 14380 * matrix. 14381 * @return the result {@link CursorAnchorInfo} to be passed to IME. 14382 * @hide 14383 */ 14384 @VisibleForTesting 14385 @Nullable 14386 public CursorAnchorInfo getCursorAnchorInfo(@InputConnection.CursorUpdateFilter int filter, 14387 @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder, 14388 @NonNull Matrix viewToScreenMatrix) { 14389 Layout layout = getLayout(); 14390 if (layout == null) { 14391 return null; 14392 } 14393 boolean includeEditorBounds = 14394 (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0; 14395 boolean includeCharacterBounds = 14396 (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0; 14397 boolean includeInsertionMarker = 14398 (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0; 14399 boolean includeVisibleLineBounds = 14400 (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0; 14401 boolean includeTextAppearance = 14402 (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0; 14403 boolean includeAll = 14404 (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker 14405 && !includeVisibleLineBounds && !includeTextAppearance); 14406 14407 includeEditorBounds |= includeAll; 14408 includeCharacterBounds |= includeAll; 14409 includeInsertionMarker |= includeAll; 14410 includeVisibleLineBounds |= includeAll; 14411 includeTextAppearance |= includeAll; 14412 14413 final CursorAnchorInfo.Builder builder = cursorAnchorInfoBuilder; 14414 builder.reset(); 14415 14416 final int selectionStart = getSelectionStart(); 14417 builder.setSelectionRange(selectionStart, getSelectionEnd()); 14418 14419 // Construct transformation matrix from view local coordinates to screen coordinates. 14420 viewToScreenMatrix.reset(); 14421 transformMatrixToGlobal(viewToScreenMatrix); 14422 builder.setMatrix(viewToScreenMatrix); 14423 14424 if (includeEditorBounds) { 14425 if (mTempRect == null) { 14426 mTempRect = new Rect(); 14427 } 14428 final Rect bounds = mTempRect; 14429 final RectF editorBounds; 14430 final RectF handwritingBounds; 14431 if (getViewVisibleRect(bounds)) { 14432 editorBounds = new RectF(bounds); 14433 handwritingBounds = new RectF(editorBounds); 14434 handwritingBounds.top -= getHandwritingBoundsOffsetTop(); 14435 handwritingBounds.left -= getHandwritingBoundsOffsetLeft(); 14436 handwritingBounds.bottom += getHandwritingBoundsOffsetBottom(); 14437 handwritingBounds.right += getHandwritingBoundsOffsetRight(); 14438 } else { 14439 // The editor is not visible at all, return empty rectangles. We still need to 14440 // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo. 14441 editorBounds = new RectF(); 14442 handwritingBounds = new RectF(); 14443 } 14444 EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder(); 14445 EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds) 14446 .setHandwritingBounds(handwritingBounds).build(); 14447 builder.setEditorBoundsInfo(editorBoundsInfo); 14448 } 14449 14450 if (includeCharacterBounds || includeInsertionMarker || includeVisibleLineBounds) { 14451 final float viewportToContentHorizontalOffset = 14452 viewportToContentHorizontalOffset(); 14453 final float viewportToContentVerticalOffset = 14454 viewportToContentVerticalOffset(); 14455 final boolean isTextTransformed = (getTransformationMethod() != null 14456 && getTransformed() instanceof OffsetMapping); 14457 if (includeCharacterBounds && !isTextTransformed) { 14458 final CharSequence text = getText(); 14459 if (text instanceof Spannable) { 14460 final Spannable sp = (Spannable) text; 14461 int composingTextStart = EditableInputConnection.getComposingSpanStart(sp); 14462 int composingTextEnd = EditableInputConnection.getComposingSpanEnd(sp); 14463 if (composingTextEnd < composingTextStart) { 14464 final int temp = composingTextEnd; 14465 composingTextEnd = composingTextStart; 14466 composingTextStart = temp; 14467 } 14468 final boolean hasComposingText = 14469 (0 <= composingTextStart) && (composingTextStart 14470 < composingTextEnd); 14471 if (hasComposingText) { 14472 final CharSequence composingText = text.subSequence(composingTextStart, 14473 composingTextEnd); 14474 builder.setComposingText(composingTextStart, composingText); 14475 populateCharacterBounds(builder, composingTextStart, 14476 composingTextEnd, viewportToContentHorizontalOffset, 14477 viewportToContentVerticalOffset); 14478 } 14479 } 14480 } 14481 14482 if (includeInsertionMarker) { 14483 // Treat selectionStart as the insertion point. 14484 if (0 <= selectionStart) { 14485 final int offsetTransformed = originalToTransformed( 14486 selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR); 14487 final int line = layout.getLineForOffset(offsetTransformed); 14488 final float insertionMarkerX = 14489 layout.getPrimaryHorizontal( 14490 offsetTransformed, layout.shouldClampCursor(line)) 14491 + viewportToContentHorizontalOffset; 14492 final float insertionMarkerTop = layout.getLineTop(line) 14493 + viewportToContentVerticalOffset; 14494 final float insertionMarkerBaseline = layout.getLineBaseline(line) 14495 + viewportToContentVerticalOffset; 14496 final float insertionMarkerBottom = 14497 layout.getLineBottom(line, /* includeLineSpacing= */ false) 14498 + viewportToContentVerticalOffset; 14499 final boolean isTopVisible = 14500 isPositionVisible(insertionMarkerX, insertionMarkerTop); 14501 final boolean isBottomVisible = 14502 isPositionVisible(insertionMarkerX, insertionMarkerBottom); 14503 int insertionMarkerFlags = 0; 14504 if (isTopVisible || isBottomVisible) { 14505 insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; 14506 } 14507 if (!isTopVisible || !isBottomVisible) { 14508 insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 14509 } 14510 if (layout.isRtlCharAt(offsetTransformed)) { 14511 insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL; 14512 } 14513 builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop, 14514 insertionMarkerBaseline, insertionMarkerBottom, 14515 insertionMarkerFlags); 14516 } 14517 } 14518 14519 if (includeVisibleLineBounds) { 14520 final Rect visibleRect = new Rect(); 14521 if (getContentVisibleRect(visibleRect)) { 14522 // Subtract the viewportToContentVerticalOffset to convert the view 14523 // coordinates to layout coordinates. 14524 final float visibleTop = 14525 visibleRect.top - viewportToContentVerticalOffset; 14526 final float visibleBottom = 14527 visibleRect.bottom - viewportToContentVerticalOffset; 14528 final int firstLine = 14529 layout.getLineForVertical((int) Math.floor(visibleTop)); 14530 final int lastLine = 14531 layout.getLineForVertical((int) Math.ceil(visibleBottom)); 14532 14533 for (int line = firstLine; line <= lastLine; ++line) { 14534 final float left = layout.getLineLeft(line) 14535 + viewportToContentHorizontalOffset; 14536 final float top = layout.getLineTop(line) 14537 + viewportToContentVerticalOffset; 14538 final float right = layout.getLineRight(line) 14539 + viewportToContentHorizontalOffset; 14540 final float bottom = layout.getLineBottom(line, false) 14541 + viewportToContentVerticalOffset; 14542 builder.addVisibleLineBounds(left, top, right, bottom); 14543 } 14544 } 14545 } 14546 } 14547 14548 if (includeTextAppearance) { 14549 builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(this)); 14550 } 14551 return builder.build(); 14552 } 14553 14554 /** 14555 * Creates the {@link TextBoundsInfo} for the text lines that intersects with the {@code rectF}. 14556 * @hide 14557 */ 14558 public TextBoundsInfo getTextBoundsInfo(@NonNull RectF bounds) { 14559 final Layout layout = getLayout(); 14560 if (layout == null) { 14561 // No valid text layout, return null. 14562 return null; 14563 } 14564 final CharSequence text = layout.getText(); 14565 if (text == null || isOffsetMappingAvailable()) { 14566 // The text is Null or the text has been transformed. Can't provide TextBoundsInfo. 14567 return null; 14568 } 14569 14570 final Matrix localToGlobalMatrix = new Matrix(); 14571 transformMatrixToGlobal(localToGlobalMatrix); 14572 final Matrix globalToLocalMatrix = new Matrix(); 14573 if (!localToGlobalMatrix.invert(globalToLocalMatrix)) { 14574 // Can't map global rectF to local coordinates, this is almost impossible in practice. 14575 return null; 14576 } 14577 14578 final float layoutLeft = viewportToContentHorizontalOffset(); 14579 final float layoutTop = viewportToContentVerticalOffset(); 14580 14581 final RectF localBounds = new RectF(bounds); 14582 globalToLocalMatrix.mapRect(localBounds); 14583 localBounds.offset(-layoutLeft, -layoutTop); 14584 14585 // Text length is 0. There is no character bounds, return empty TextBoundsInfo. 14586 // rectF doesn't intersect with the layout, return empty TextBoundsInfo. 14587 if (!localBounds.intersects(0f, 0f, layout.getWidth(), layout.getHeight()) 14588 || text.length() == 0) { 14589 final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(0, 0); 14590 final SegmentFinder emptySegmentFinder = 14591 new SegmentFinder.PrescribedSegmentFinder(new int[0]); 14592 builder.setMatrix(localToGlobalMatrix) 14593 .setCharacterBounds(new float[0]) 14594 .setCharacterBidiLevel(new int[0]) 14595 .setCharacterFlags(new int[0]) 14596 .setGraphemeSegmentFinder(emptySegmentFinder) 14597 .setLineSegmentFinder(emptySegmentFinder) 14598 .setWordSegmentFinder(emptySegmentFinder); 14599 return builder.build(); 14600 } 14601 14602 final int startLine = layout.getLineForVertical((int) Math.floor(localBounds.top)); 14603 final int endLine = layout.getLineForVertical((int) Math.floor(localBounds.bottom)); 14604 final int start = layout.getLineStart(startLine); 14605 final int end = layout.getLineEnd(endLine); 14606 14607 // Compute character bounds. 14608 final float[] characterBounds = getCharacterBounds(start, end, layoutLeft, layoutTop); 14609 14610 // Compute character flags and BiDi levels. 14611 final int[] characterFlags = new int[end - start]; 14612 final int[] characterBidiLevels = new int[end - start]; 14613 for (int line = startLine; line <= endLine; ++line) { 14614 final int lineStart = layout.getLineStart(line); 14615 final int lineEnd = layout.getLineEnd(line); 14616 final Layout.Directions directions = layout.getLineDirections(line); 14617 for (int i = 0; i < directions.getRunCount(); ++i) { 14618 final int runStart = directions.getRunStart(i) + lineStart; 14619 final int runEnd = Math.min(runStart + directions.getRunLength(i), lineEnd); 14620 final int runLevel = directions.getRunLevel(i); 14621 Arrays.fill(characterBidiLevels, runStart - start, runEnd - start, runLevel); 14622 } 14623 14624 final boolean lineIsRtl = 14625 layout.getParagraphDirection(line) == Layout.DIR_RIGHT_TO_LEFT; 14626 for (int index = lineStart; index < lineEnd; ++index) { 14627 int flags = 0; 14628 if (TextUtils.isWhitespace(text.charAt(index))) { 14629 flags |= TextBoundsInfo.FLAG_CHARACTER_WHITESPACE; 14630 } 14631 if (TextUtils.isPunctuation(Character.codePointAt(text, index))) { 14632 flags |= TextBoundsInfo.FLAG_CHARACTER_PUNCTUATION; 14633 } 14634 if (TextUtils.isNewline(Character.codePointAt(text, index))) { 14635 flags |= TextBoundsInfo.FLAG_CHARACTER_LINEFEED; 14636 } 14637 if (lineIsRtl) { 14638 flags |= TextBoundsInfo.FLAG_LINE_IS_RTL; 14639 } 14640 characterFlags[index - start] = flags; 14641 } 14642 } 14643 14644 // Create grapheme SegmentFinder. 14645 final SegmentFinder graphemeSegmentFinder = 14646 new GraphemeClusterSegmentFinder(text, layout.getPaint()); 14647 14648 // Create word SegmentFinder. 14649 final WordIterator wordIterator = getWordIterator(); 14650 wordIterator.setCharSequence(text, 0, text.length()); 14651 final SegmentFinder wordSegmentFinder = new WordSegmentFinder(text, wordIterator); 14652 14653 // Create line SegmentFinder. 14654 final int lineCount = endLine - startLine + 1; 14655 final int[] lineRanges = new int[2 * lineCount]; 14656 for (int line = startLine; line <= endLine; ++line) { 14657 final int offset = line - startLine; 14658 lineRanges[2 * offset] = layout.getLineStart(line); 14659 lineRanges[2 * offset + 1] = layout.getLineEnd(line); 14660 } 14661 final SegmentFinder lineSegmentFinder = 14662 new SegmentFinder.PrescribedSegmentFinder(lineRanges); 14663 14664 return new TextBoundsInfo.Builder(start, end) 14665 .setMatrix(localToGlobalMatrix) 14666 .setCharacterBounds(characterBounds) 14667 .setCharacterBidiLevel(characterBidiLevels) 14668 .setCharacterFlags(characterFlags) 14669 .setGraphemeSegmentFinder(graphemeSegmentFinder) 14670 .setLineSegmentFinder(lineSegmentFinder) 14671 .setWordSegmentFinder(wordSegmentFinder) 14672 .build(); 14673 } 14674 14675 /** 14676 * @hide 14677 */ 14678 public boolean isPositionVisible(final float positionX, final float positionY) { 14679 synchronized (TEMP_POSITION) { 14680 final float[] position = TEMP_POSITION; 14681 position[0] = positionX; 14682 position[1] = positionY; 14683 View view = this; 14684 14685 while (view != null) { 14686 if (view != this) { 14687 // Local scroll is already taken into account in positionX/Y 14688 position[0] -= view.getScrollX(); 14689 position[1] -= view.getScrollY(); 14690 } 14691 14692 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth() 14693 || position[1] > view.getHeight()) { 14694 return false; 14695 } 14696 14697 if (!view.getMatrix().isIdentity()) { 14698 view.getMatrix().mapPoints(position); 14699 } 14700 14701 position[0] += view.getLeft(); 14702 position[1] += view.getTop(); 14703 14704 final ViewParent parent = view.getParent(); 14705 if (parent instanceof View) { 14706 view = (View) parent; 14707 } else { 14708 // We've reached the ViewRoot, stop iterating 14709 view = null; 14710 } 14711 } 14712 } 14713 14714 // We've been able to walk up the view hierarchy and the position was never clipped 14715 return true; 14716 } 14717 14718 /** 14719 * Performs an accessibility action after it has been offered to the 14720 * delegate. 14721 * 14722 * @hide 14723 */ 14724 @Override 14725 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 14726 if (mEditor != null) { 14727 if (mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action) 14728 || mEditor.performSmartActionsAccessibilityAction(action)) { 14729 return true; 14730 } 14731 } 14732 switch (action) { 14733 case AccessibilityNodeInfo.ACTION_CLICK: { 14734 return performAccessibilityActionClick(arguments); 14735 } 14736 case AccessibilityNodeInfo.ACTION_COPY: { 14737 if (isFocused() && canCopy()) { 14738 if (onTextContextMenuItem(ID_COPY)) { 14739 return true; 14740 } 14741 } 14742 } return false; 14743 case AccessibilityNodeInfo.ACTION_PASTE: { 14744 if (isFocused() && canPaste()) { 14745 if (onTextContextMenuItem(ID_PASTE)) { 14746 return true; 14747 } 14748 } 14749 } return false; 14750 case AccessibilityNodeInfo.ACTION_CUT: { 14751 if (isFocused() && canCut()) { 14752 if (onTextContextMenuItem(ID_CUT)) { 14753 return true; 14754 } 14755 } 14756 } return false; 14757 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 14758 ensureIterableTextForAccessibilitySelectable(); 14759 CharSequence text = getIterableTextForAccessibility(); 14760 if (text == null) { 14761 return false; 14762 } 14763 final int start = (arguments != null) ? arguments.getInt( 14764 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 14765 final int end = (arguments != null) ? arguments.getInt( 14766 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 14767 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 14768 // No arguments clears the selection. 14769 if (start == end && end == -1) { 14770 Selection.removeSelection((Spannable) text); 14771 return true; 14772 } 14773 if (start >= 0 && start <= end && end <= text.length()) { 14774 requestFocusOnNonEditableSelectableText(); 14775 Selection.setSelection((Spannable) text, start, end); 14776 // Make sure selection mode is engaged. 14777 if (mEditor != null) { 14778 mEditor.startSelectionActionModeAsync(false); 14779 } 14780 return true; 14781 } 14782 } 14783 } return false; 14784 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 14785 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 14786 ensureIterableTextForAccessibilitySelectable(); 14787 return super.performAccessibilityActionInternal(action, arguments); 14788 } 14789 case ACCESSIBILITY_ACTION_SHARE: { 14790 if (isFocused() && canShare()) { 14791 if (onTextContextMenuItem(ID_SHARE)) { 14792 return true; 14793 } 14794 } 14795 } return false; 14796 case AccessibilityNodeInfo.ACTION_SET_TEXT: { 14797 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) { 14798 return false; 14799 } 14800 CharSequence text = (arguments != null) ? arguments.getCharSequence( 14801 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; 14802 setText(text); 14803 if (mText != null) { 14804 int updatedTextLength = mText.length(); 14805 if (updatedTextLength > 0) { 14806 Selection.setSelection(mSpannable, updatedTextLength); 14807 } 14808 } 14809 } return true; 14810 case R.id.accessibilityActionImeEnter: { 14811 if (isFocused() && isTextEditable()) { 14812 onEditorAction(getImeActionId()); 14813 } 14814 } return true; 14815 case AccessibilityNodeInfo.ACTION_LONG_CLICK: { 14816 if (isLongClickable()) { 14817 boolean handled; 14818 if (isEnabled() && (mBufferType == BufferType.EDITABLE)) { 14819 mEditor.mIsBeingLongClickedByAccessibility = true; 14820 try { 14821 handled = performLongClick(); 14822 } finally { 14823 mEditor.mIsBeingLongClickedByAccessibility = false; 14824 } 14825 } else { 14826 handled = performLongClick(); 14827 } 14828 return handled; 14829 } 14830 } 14831 return false; 14832 default: { 14833 // New ids have static blocks to assign values, so they can't be used in a case 14834 // block. 14835 if (action == R.id.accessibilityActionShowTextSuggestions) { 14836 return isFocused() && canReplace() && onTextContextMenuItem(ID_REPLACE); 14837 } 14838 return super.performAccessibilityActionInternal(action, arguments); 14839 } 14840 } 14841 } 14842 14843 private boolean performAccessibilityActionClick(Bundle arguments) { 14844 boolean handled = false; 14845 14846 if (!isEnabled()) { 14847 return false; 14848 } 14849 14850 if (isClickable() || isLongClickable()) { 14851 // Simulate View.onTouchEvent for an ACTION_UP event 14852 if (isFocusable() && !isFocused()) { 14853 requestFocus(); 14854 } 14855 14856 performClick(); 14857 handled = true; 14858 } 14859 14860 // Show the IME, except when selecting in read-only text. 14861 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null 14862 && (isTextEditable() || isTextSelectable()) && isFocused()) { 14863 final InputMethodManager imm = getInputMethodManager(); 14864 viewClicked(imm); 14865 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 14866 handled |= imm.showSoftInput(this, 0); 14867 } 14868 } 14869 14870 return handled; 14871 } 14872 14873 private void requestFocusOnNonEditableSelectableText() { 14874 if (!isTextEditable() && isTextSelectable()) { 14875 if (!isEnabled()) { 14876 return; 14877 } 14878 14879 if (isFocusable() && !isFocused()) { 14880 requestFocus(); 14881 } 14882 } 14883 } 14884 14885 private boolean hasSpannableText() { 14886 return mText != null && mText instanceof Spannable; 14887 } 14888 14889 /** @hide */ 14890 @Override 14891 public void sendAccessibilityEventInternal(int eventType) { 14892 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) { 14893 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions(); 14894 } 14895 14896 super.sendAccessibilityEventInternal(eventType); 14897 } 14898 14899 @Override 14900 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { 14901 // Do not send scroll events since first they are not interesting for 14902 // accessibility and second such events a generated too frequently. 14903 // For details see the implementation of bringTextIntoView(). 14904 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 14905 return; 14906 } 14907 super.sendAccessibilityEventUnchecked(event); 14908 } 14909 14910 /** 14911 * Returns the text that should be exposed to accessibility services. 14912 * <p> 14913 * This approximates what is displayed visually. 14914 * 14915 * @return the text that should be exposed to accessibility services, may 14916 * be {@code null} if no text is set 14917 */ 14918 @Nullable 14919 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 14920 private CharSequence getTextForAccessibility() { 14921 // If the text is empty, we must be showing the hint text. 14922 if (TextUtils.isEmpty(mText)) { 14923 return mHint; 14924 } 14925 14926 // Otherwise, return whatever text is being displayed. 14927 return TextUtils.trimToParcelableSize(mTransformed); 14928 } 14929 14930 boolean isVisibleToAccessibility() { 14931 return AccessibilityManager.getInstance(mContext).isEnabled() 14932 && (isFocused() || (isSelected() && isShown())); 14933 } 14934 14935 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 14936 int fromIndex, int removedCount, int addedCount) { 14937 AccessibilityEvent event = 14938 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 14939 event.setFromIndex(fromIndex); 14940 event.setRemovedCount(removedCount); 14941 event.setAddedCount(addedCount); 14942 event.setBeforeText(beforeText); 14943 sendAccessibilityEventUnchecked(event); 14944 } 14945 14946 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 14947 int fromIndex, int toIndex) { 14948 AccessibilityEvent event = 14949 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 14950 event.setFromIndex(fromIndex); 14951 event.setToIndex(toIndex); 14952 event.setBeforeText(beforeText); 14953 sendAccessibilityEventUnchecked(event); 14954 } 14955 14956 private InputMethodManager getInputMethodManager() { 14957 return getContext().getSystemService(InputMethodManager.class); 14958 } 14959 14960 /** 14961 * Returns whether this text view is a current input method target. The 14962 * default implementation just checks with {@link InputMethodManager}. 14963 * @return True if the TextView is a current input method target; false otherwise. 14964 */ 14965 public boolean isInputMethodTarget() { 14966 InputMethodManager imm = getInputMethodManager(); 14967 return imm != null && imm.isActive(this); 14968 } 14969 14970 static final int ID_SELECT_ALL = android.R.id.selectAll; 14971 static final int ID_UNDO = android.R.id.undo; 14972 static final int ID_REDO = android.R.id.redo; 14973 static final int ID_CUT = android.R.id.cut; 14974 static final int ID_COPY = android.R.id.copy; 14975 static final int ID_PASTE = android.R.id.paste; 14976 static final int ID_SHARE = android.R.id.shareText; 14977 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; 14978 static final int ID_REPLACE = android.R.id.replaceText; 14979 static final int ID_ASSIST = android.R.id.textAssist; 14980 static final int ID_AUTOFILL = android.R.id.autofill; 14981 14982 /** 14983 * Called when a context menu option for the text view is selected. Currently 14984 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 14985 * {@link android.R.id#copy}, {@link android.R.id#paste}, 14986 * {@link android.R.id#pasteAsPlainText} (starting at API level 23) or 14987 * {@link android.R.id#shareText}. 14988 * 14989 * @return true if the context menu item action was performed. 14990 */ 14991 public boolean onTextContextMenuItem(int id) { 14992 int min = 0; 14993 int max = mText.length(); 14994 14995 if (isFocused()) { 14996 final int selStart = getSelectionStart(); 14997 final int selEnd = getSelectionEnd(); 14998 14999 min = Math.max(0, Math.min(selStart, selEnd)); 15000 max = Math.max(0, Math.max(selStart, selEnd)); 15001 } 15002 15003 switch (id) { 15004 case ID_SELECT_ALL: 15005 final boolean hadSelection = hasSelection(); 15006 selectAllText(); 15007 if (mEditor != null && hadSelection) { 15008 mEditor.invalidateActionModeAsync(); 15009 } 15010 return true; 15011 15012 case ID_UNDO: 15013 if (mEditor != null) { 15014 mEditor.undo(); 15015 } 15016 return true; // Returns true even if nothing was undone. 15017 15018 case ID_REDO: 15019 if (mEditor != null) { 15020 mEditor.redo(); 15021 } 15022 return true; // Returns true even if nothing was undone. 15023 15024 case ID_PASTE: 15025 paste(true /* withFormatting */); 15026 return true; 15027 15028 case ID_PASTE_AS_PLAIN_TEXT: 15029 paste(false /* withFormatting */); 15030 return true; 15031 15032 case ID_CUT: 15033 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max)); 15034 if (setPrimaryClip(cutData)) { 15035 deleteText_internal(min, max); 15036 } else { 15037 Toast.makeText(getContext(), 15038 com.android.internal.R.string.failed_to_copy_to_clipboard, 15039 Toast.LENGTH_SHORT).show(); 15040 } 15041 return true; 15042 15043 case ID_COPY: 15044 // For link action mode in a non-selectable/non-focusable TextView, 15045 // make sure that we set the appropriate min/max. 15046 final int selStart = getSelectionStart(); 15047 final int selEnd = getSelectionEnd(); 15048 min = Math.max(0, Math.min(selStart, selEnd)); 15049 max = Math.max(0, Math.max(selStart, selEnd)); 15050 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max)); 15051 if (setPrimaryClip(copyData)) { 15052 stopTextActionMode(); 15053 } else { 15054 Toast.makeText(getContext(), 15055 com.android.internal.R.string.failed_to_copy_to_clipboard, 15056 Toast.LENGTH_SHORT).show(); 15057 } 15058 return true; 15059 15060 case ID_REPLACE: 15061 if (mEditor != null) { 15062 mEditor.replace(); 15063 } 15064 return true; 15065 15066 case ID_SHARE: 15067 shareSelectedText(); 15068 return true; 15069 15070 case ID_AUTOFILL: 15071 requestAutofill(); 15072 stopTextActionMode(); 15073 return true; 15074 } 15075 return false; 15076 } 15077 15078 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 15079 CharSequence getTransformedText(int start, int end) { 15080 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 15081 } 15082 15083 @Override 15084 public boolean performLongClick() { 15085 if (DEBUG_CURSOR) { 15086 logCursor("performLongClick", null); 15087 } 15088 15089 boolean handled = false; 15090 boolean performedHapticFeedback = false; 15091 15092 if (mEditor != null) { 15093 mEditor.mIsBeingLongClicked = true; 15094 } 15095 15096 if (super.performLongClick()) { 15097 handled = true; 15098 performedHapticFeedback = true; 15099 } 15100 15101 if (mEditor != null) { 15102 handled |= mEditor.performLongClick(handled); 15103 mEditor.mIsBeingLongClicked = false; 15104 } 15105 15106 if (handled) { 15107 if (!performedHapticFeedback) { 15108 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 15109 } 15110 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 15111 } else { 15112 MetricsLogger.action( 15113 mContext, 15114 MetricsEvent.TEXT_LONGPRESS, 15115 TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER); 15116 } 15117 15118 return handled; 15119 } 15120 15121 @Override 15122 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 15123 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 15124 if (mEditor != null) { 15125 mEditor.onScrollChanged(); 15126 } 15127 } 15128 15129 /** 15130 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 15131 * by the IME or by the spell checker as the user types. This is done by adding 15132 * {@link SuggestionSpan}s to the text. 15133 * 15134 * When suggestions are enabled (default), this list of suggestions will be displayed when the 15135 * user asks for them on these parts of the text. This value depends on the inputType of this 15136 * TextView. 15137 * 15138 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 15139 * 15140 * In addition, the type variation must be one of 15141 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 15142 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 15143 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 15144 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 15145 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 15146 * 15147 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 15148 * 15149 * @return true if the suggestions popup window is enabled, based on the inputType. 15150 */ 15151 public boolean isSuggestionsEnabled() { 15152 if (mEditor == null) return false; 15153 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 15154 return false; 15155 } 15156 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 15157 15158 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 15159 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL 15160 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT 15161 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE 15162 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE 15163 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 15164 } 15165 15166 /** 15167 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 15168 * selection is initiated in this View. 15169 * 15170 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy, 15171 * Paste, Replace and Share actions, depending on what this View supports. 15172 * 15173 * <p>A custom implementation can add new entries in the default menu in its 15174 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} 15175 * method. The default actions can also be removed from the menu using 15176 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 15177 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, 15178 * {@link android.R.id#pasteAsPlainText} (starting at API level 23), 15179 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. 15180 * 15181 * <p>Returning false from 15182 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} 15183 * will prevent the action mode from being started. 15184 * 15185 * <p>Action click events should be handled by the custom implementation of 15186 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, 15187 * android.view.MenuItem)}. 15188 * 15189 * <p>Note that text selection mode is not started when a TextView receives focus and the 15190 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 15191 * that case, to allow for quick replacement. 15192 */ 15193 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 15194 createEditorIfNeeded(); 15195 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 15196 } 15197 15198 /** 15199 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 15200 * 15201 * @return The current custom selection callback. 15202 */ 15203 public ActionMode.Callback getCustomSelectionActionModeCallback() { 15204 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 15205 } 15206 15207 /** 15208 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 15209 * insertion is initiated in this View. 15210 * The standard implementation populates the menu with a subset of Select All, 15211 * Paste and Replace actions, depending on what this View supports. 15212 * 15213 * <p>A custom implementation can add new entries in the default menu in its 15214 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, 15215 * android.view.Menu)} method. The default actions can also be removed from the menu using 15216 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 15217 * {@link android.R.id#paste}, {@link android.R.id#pasteAsPlainText} (starting at API 15218 * level 23) or {@link android.R.id#replaceText} ids as parameters.</p> 15219 * 15220 * <p>Returning false from 15221 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, 15222 * android.view.Menu)} will prevent the action mode from being started.</p> 15223 * 15224 * <p>Action click events should be handled by the custom implementation of 15225 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, 15226 * android.view.MenuItem)}.</p> 15227 * 15228 * <p>Note that text insertion mode is not started when a TextView receives focus and the 15229 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p> 15230 */ 15231 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { 15232 createEditorIfNeeded(); 15233 mEditor.mCustomInsertionActionModeCallback = actionModeCallback; 15234 } 15235 15236 /** 15237 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. 15238 * 15239 * @return The current custom insertion callback. 15240 */ 15241 public ActionMode.Callback getCustomInsertionActionModeCallback() { 15242 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; 15243 } 15244 15245 /** 15246 * Sets the {@link TextClassifier} for this TextView. 15247 */ 15248 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 15249 mTextClassifier = textClassifier; 15250 } 15251 15252 /** 15253 * Returns the {@link TextClassifier} used by this TextView. 15254 * If no TextClassifier has been set, this TextView uses the default set by the 15255 * {@link TextClassificationManager}. 15256 */ 15257 @NonNull 15258 public TextClassifier getTextClassifier() { 15259 if (mTextClassifier == null) { 15260 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 15261 if (tcm != null) { 15262 return tcm.getTextClassifier(); 15263 } 15264 return TextClassifier.NO_OP; 15265 } 15266 return mTextClassifier; 15267 } 15268 15269 /** 15270 * Returns a session-aware text classifier. 15271 * This method creates one if none already exists or the current one is destroyed. 15272 */ 15273 @NonNull 15274 TextClassifier getTextClassificationSession() { 15275 if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) { 15276 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 15277 if (tcm != null) { 15278 final String widgetType; 15279 if (isTextEditable()) { 15280 widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT; 15281 } else if (isTextSelectable()) { 15282 widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW; 15283 } else { 15284 widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW; 15285 } 15286 mTextClassificationContext = new TextClassificationContext.Builder( 15287 mContext.getPackageName(), widgetType) 15288 .build(); 15289 if (mTextClassifier != null) { 15290 mTextClassificationSession = tcm.createTextClassificationSession( 15291 mTextClassificationContext, mTextClassifier); 15292 } else { 15293 mTextClassificationSession = tcm.createTextClassificationSession( 15294 mTextClassificationContext); 15295 } 15296 } else { 15297 mTextClassificationSession = TextClassifier.NO_OP; 15298 } 15299 } 15300 return mTextClassificationSession; 15301 } 15302 15303 /** 15304 * Returns the {@link TextClassificationContext} for the current TextClassifier session. 15305 * @see #getTextClassificationSession() 15306 */ 15307 @Nullable 15308 TextClassificationContext getTextClassificationContext() { 15309 return mTextClassificationContext; 15310 } 15311 15312 /** 15313 * Returns true if this TextView uses a no-op TextClassifier. 15314 */ 15315 boolean usesNoOpTextClassifier() { 15316 return getTextClassifier() == TextClassifier.NO_OP; 15317 } 15318 15319 /** 15320 * Starts an ActionMode for the specified TextLinkSpan. 15321 * 15322 * @return Whether or not we're attempting to start the action mode. 15323 * @hide 15324 */ 15325 public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) { 15326 Preconditions.checkNotNull(clickedSpan); 15327 15328 if (!(mText instanceof Spanned)) { 15329 return false; 15330 } 15331 15332 final int start = ((Spanned) mText).getSpanStart(clickedSpan); 15333 final int end = ((Spanned) mText).getSpanEnd(clickedSpan); 15334 15335 if (start < 0 || end > mText.length() || start >= end) { 15336 return false; 15337 } 15338 15339 createEditorIfNeeded(); 15340 mEditor.startLinkActionModeAsync(start, end); 15341 return true; 15342 } 15343 15344 /** 15345 * Handles a click on the specified TextLinkSpan. 15346 * 15347 * @return Whether or not the click is being handled. 15348 * @hide 15349 */ 15350 public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) { 15351 Preconditions.checkNotNull(clickedSpan); 15352 if (mText instanceof Spanned) { 15353 final Spanned spanned = (Spanned) mText; 15354 final int start = spanned.getSpanStart(clickedSpan); 15355 final int end = spanned.getSpanEnd(clickedSpan); 15356 if (start >= 0 && end <= mText.length() && start < end) { 15357 final TextClassification.Request request = new TextClassification.Request.Builder( 15358 mText, start, end) 15359 .setDefaultLocales(getTextLocales()) 15360 .build(); 15361 final Supplier<TextClassification> supplier = () -> 15362 getTextClassificationSession().classifyText(request); 15363 final Consumer<TextClassification> consumer = classification -> { 15364 if (classification != null) { 15365 if (!classification.getActions().isEmpty()) { 15366 try { 15367 classification.getActions().get(0).getActionIntent().send(); 15368 } catch (PendingIntent.CanceledException e) { 15369 Log.e(LOG_TAG, "Error sending PendingIntent", e); 15370 } 15371 } else { 15372 Log.d(LOG_TAG, "No link action to perform"); 15373 } 15374 } else { 15375 // classification == null 15376 Log.d(LOG_TAG, "Timeout while classifying text"); 15377 } 15378 }; 15379 CompletableFuture.supplyAsync(supplier) 15380 .completeOnTimeout(null, 1, TimeUnit.SECONDS) 15381 .thenAccept(consumer); 15382 return true; 15383 } 15384 } 15385 return false; 15386 } 15387 15388 /** 15389 * @hide 15390 */ 15391 @UnsupportedAppUsage 15392 protected void stopTextActionMode() { 15393 if (mEditor != null) { 15394 mEditor.stopTextActionMode(); 15395 } 15396 } 15397 15398 /** @hide */ 15399 public void hideFloatingToolbar(int durationMs) { 15400 if (mEditor != null) { 15401 mEditor.hideFloatingToolbar(durationMs); 15402 } 15403 } 15404 15405 boolean canUndo() { 15406 return mEditor != null && mEditor.canUndo(); 15407 } 15408 15409 boolean canRedo() { 15410 return mEditor != null && mEditor.canRedo(); 15411 } 15412 15413 boolean canCut() { 15414 if (hasPasswordTransformationMethod()) { 15415 return false; 15416 } 15417 15418 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null 15419 && mEditor.mKeyListener != null) { 15420 return true; 15421 } 15422 15423 return false; 15424 } 15425 15426 boolean canCopy() { 15427 if (hasPasswordTransformationMethod()) { 15428 return false; 15429 } 15430 15431 if (mText.length() > 0 && hasSelection() && mEditor != null) { 15432 return true; 15433 } 15434 15435 return false; 15436 } 15437 15438 boolean canReplace() { 15439 if (hasPasswordTransformationMethod()) { 15440 return false; 15441 } 15442 15443 return (mText.length() > 0) && (mText instanceof Editable) && (mEditor != null) 15444 && isSuggestionsEnabled() && mEditor.shouldOfferToShowSuggestions(); 15445 } 15446 15447 boolean canShare() { 15448 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned() 15449 || !getContext().getResources().getBoolean( 15450 com.android.internal.R.bool.config_textShareSupported)) { 15451 return false; 15452 } 15453 return canCopy(); 15454 } 15455 15456 boolean isDeviceProvisioned() { 15457 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) { 15458 mDeviceProvisionedState = Settings.Global.getInt( 15459 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0 15460 ? DEVICE_PROVISIONED_YES 15461 : DEVICE_PROVISIONED_NO; 15462 } 15463 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; 15464 } 15465 15466 @UnsupportedAppUsage 15467 boolean canPaste() { 15468 return (mText instanceof Editable 15469 && mEditor != null && mEditor.mKeyListener != null 15470 && getSelectionStart() >= 0 15471 && getSelectionEnd() >= 0 15472 && getClipboardManagerForUser().hasPrimaryClip()); 15473 } 15474 15475 boolean canPasteAsPlainText() { 15476 if (!canPaste()) { 15477 return false; 15478 } 15479 15480 final ClipDescription description = 15481 getClipboardManagerForUser().getPrimaryClipDescription(); 15482 if (description == null) { 15483 return false; 15484 } 15485 final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 15486 return (isPlainType && description.isStyledText()) 15487 || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); 15488 } 15489 15490 boolean canProcessText() { 15491 if (getId() == View.NO_ID) { 15492 return false; 15493 } 15494 return canShare(); 15495 } 15496 15497 boolean canSelectAllText() { 15498 return canSelectText() && !hasPasswordTransformationMethod() 15499 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); 15500 } 15501 15502 boolean selectAllText() { 15503 if (mEditor != null) { 15504 // Hide the toolbar before changing the selection to avoid flickering. 15505 hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY); 15506 } 15507 final int length = mText.length(); 15508 Selection.setSelection(mSpannable, 0, length); 15509 return length > 0; 15510 } 15511 15512 private void paste(boolean withFormatting) { 15513 ClipboardManager clipboard = getClipboardManagerForUser(); 15514 ClipData clip = clipboard.getPrimaryClip(); 15515 if (clip == null) { 15516 return; 15517 } 15518 final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD) 15519 .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT) 15520 .build(); 15521 performReceiveContent(payload); 15522 sLastCutCopyOrTextChangedTime = 0; 15523 } 15524 15525 private void shareSelectedText() { 15526 String selectedText = getSelectedText(); 15527 if (selectedText != null && !selectedText.isEmpty()) { 15528 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); 15529 sharingIntent.setType("text/plain"); 15530 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); 15531 selectedText = TextUtils.trimToParcelableSize(selectedText); 15532 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); 15533 getContext().startActivity(Intent.createChooser(sharingIntent, null)); 15534 Selection.setSelection(mSpannable, getSelectionEnd()); 15535 } 15536 } 15537 15538 @CheckResult 15539 private boolean setPrimaryClip(ClipData clip) { 15540 ClipboardManager clipboard = getClipboardManagerForUser(); 15541 try { 15542 clipboard.setPrimaryClip(clip); 15543 } catch (Throwable t) { 15544 return false; 15545 } 15546 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis(); 15547 return true; 15548 } 15549 15550 /** 15551 * Get the character offset closest to the specified absolute position. A typical use case is to 15552 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 15553 * 15554 * @param x The horizontal absolute position of a point on screen 15555 * @param y The vertical absolute position of a point on screen 15556 * @return the character offset for the character whose position is closest to the specified 15557 * position. Returns -1 if there is no layout. 15558 */ 15559 public int getOffsetForPosition(float x, float y) { 15560 if (getLayout() == null) return -1; 15561 final int line = getLineAtCoordinate(y); 15562 final int offset = getOffsetAtCoordinate(line, x); 15563 return offset; 15564 } 15565 15566 float convertToLocalHorizontalCoordinate(float x) { 15567 x -= getTotalPaddingLeft(); 15568 // Clamp the position to inside of the view. 15569 x = Math.max(0.0f, x); 15570 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 15571 x += getScrollX(); 15572 return x; 15573 } 15574 15575 /** @hide */ 15576 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 15577 public int getLineAtCoordinate(float y) { 15578 y -= getTotalPaddingTop(); 15579 // Clamp the position to inside of the view. 15580 y = Math.max(0.0f, y); 15581 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 15582 y += getScrollY(); 15583 return getLayout().getLineForVertical((int) y); 15584 } 15585 15586 int getLineAtCoordinateUnclamped(float y) { 15587 y -= getTotalPaddingTop(); 15588 y += getScrollY(); 15589 return getLayout().getLineForVertical((int) y); 15590 } 15591 15592 int getOffsetAtCoordinate(int line, float x) { 15593 x = convertToLocalHorizontalCoordinate(x); 15594 final int offset = getLayout().getOffsetForHorizontal(line, x); 15595 return transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CURSOR); 15596 } 15597 15598 /** 15599 * Convenient method to convert an offset on the transformed text to the original text. 15600 * @hide 15601 */ 15602 public int transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy) { 15603 if (getTransformationMethod() == null) { 15604 return offset; 15605 } 15606 if (mTransformed instanceof OffsetMapping) { 15607 final OffsetMapping transformedText = (OffsetMapping) mTransformed; 15608 return transformedText.transformedToOriginal(offset, strategy); 15609 } 15610 return offset; 15611 } 15612 15613 /** 15614 * Convenient method to convert an offset on the original text to the transformed text. 15615 * @hide 15616 */ 15617 public int originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy) { 15618 if (getTransformationMethod() == null) { 15619 return offset; 15620 } 15621 if (mTransformed instanceof OffsetMapping) { 15622 final OffsetMapping transformedText = (OffsetMapping) mTransformed; 15623 return transformedText.originalToTransformed(offset, strategy); 15624 } 15625 return offset; 15626 } 15627 /** 15628 * Handles drag events sent by the system following a call to 15629 * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int) 15630 * startDragAndDrop()}. 15631 * 15632 * <p>If this text view is not editable, delegates to the default {@link View#onDragEvent} 15633 * implementation. 15634 * 15635 * <p>If this text view is editable, accepts all drag actions (returns true for an 15636 * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event and all 15637 * subsequent drag events). While the drag is in progress, updates the cursor position 15638 * to follow the touch location. Once a drop event is received, handles content insertion 15639 * via {@link #performReceiveContent}. 15640 * 15641 * @param event The {@link android.view.DragEvent} sent by the system. 15642 * The {@link android.view.DragEvent#getAction()} method returns an action type constant 15643 * defined in DragEvent, indicating the type of drag event represented by this object. 15644 * @return Returns true if this text view is editable and delegates to super otherwise. 15645 * See {@link View#onDragEvent}. 15646 */ 15647 @Override 15648 public boolean onDragEvent(DragEvent event) { 15649 if (mEditor == null || !mEditor.hasInsertionController()) { 15650 // If this TextView is not editable, defer to the default View implementation. This 15651 // will check for the presence of an OnReceiveContentListener and accept/reject 15652 // drag events depending on whether the listener is/isn't set. 15653 return super.onDragEvent(event); 15654 } 15655 switch (event.getAction()) { 15656 case DragEvent.ACTION_DRAG_STARTED: 15657 return true; 15658 15659 case DragEvent.ACTION_DRAG_ENTERED: 15660 TextView.this.requestFocus(); 15661 return true; 15662 15663 case DragEvent.ACTION_DRAG_LOCATION: 15664 if (mText instanceof Spannable) { 15665 final int offset = getOffsetForPosition(event.getX(), event.getY()); 15666 Selection.setSelection(mSpannable, offset); 15667 } 15668 return true; 15669 15670 case DragEvent.ACTION_DROP: 15671 if (mEditor != null) mEditor.onDrop(event); 15672 return true; 15673 15674 case DragEvent.ACTION_DRAG_ENDED: 15675 case DragEvent.ACTION_DRAG_EXITED: 15676 default: 15677 return true; 15678 } 15679 } 15680 15681 boolean isInBatchEditMode() { 15682 if (mEditor == null) return false; 15683 final Editor.InputMethodState ims = mEditor.mInputMethodState; 15684 if (ims != null) { 15685 return ims.mBatchEditNesting > 0; 15686 } 15687 return mEditor.mInBatchEditControllers; 15688 } 15689 15690 @Override 15691 public void onRtlPropertiesChanged(int layoutDirection) { 15692 super.onRtlPropertiesChanged(layoutDirection); 15693 15694 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic(); 15695 if (mTextDir != newTextDir) { 15696 mTextDir = newTextDir; 15697 if (mLayout != null) { 15698 checkForRelayout(); 15699 } 15700 } 15701 } 15702 15703 /** 15704 * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout. 15705 * The {@link TextDirectionHeuristic} that is used by TextView is only available after 15706 * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the 15707 * return value may not be the same as the one TextView uses if the View's layout direction is 15708 * not resolved or detached from parent root view. 15709 */ 15710 public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() { 15711 if (hasPasswordTransformationMethod()) { 15712 // passwords fields should be LTR 15713 return TextDirectionHeuristics.LTR; 15714 } 15715 15716 if (mEditor != null 15717 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 15718 == EditorInfo.TYPE_CLASS_PHONE) { 15719 // Phone numbers must be in the direction of the locale's digits. Most locales have LTR 15720 // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have 15721 // RTL digits. 15722 final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale()); 15723 final String zero = symbols.getDigitStrings()[0]; 15724 // In case the zero digit is multi-codepoint, just use the first codepoint to determine 15725 // direction. 15726 final int firstCodepoint = zero.codePointAt(0); 15727 final byte digitDirection = Character.getDirectionality(firstCodepoint); 15728 if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT 15729 || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) { 15730 return TextDirectionHeuristics.RTL; 15731 } else { 15732 return TextDirectionHeuristics.LTR; 15733 } 15734 } 15735 15736 // Always need to resolve layout direction first 15737 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 15738 15739 // Now, we can select the heuristic 15740 switch (getTextDirection()) { 15741 default: 15742 case TEXT_DIRECTION_FIRST_STRONG: 15743 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 15744 TextDirectionHeuristics.FIRSTSTRONG_LTR); 15745 case TEXT_DIRECTION_ANY_RTL: 15746 return TextDirectionHeuristics.ANYRTL_LTR; 15747 case TEXT_DIRECTION_LTR: 15748 return TextDirectionHeuristics.LTR; 15749 case TEXT_DIRECTION_RTL: 15750 return TextDirectionHeuristics.RTL; 15751 case TEXT_DIRECTION_LOCALE: 15752 return TextDirectionHeuristics.LOCALE; 15753 case TEXT_DIRECTION_FIRST_STRONG_LTR: 15754 return TextDirectionHeuristics.FIRSTSTRONG_LTR; 15755 case TEXT_DIRECTION_FIRST_STRONG_RTL: 15756 return TextDirectionHeuristics.FIRSTSTRONG_RTL; 15757 } 15758 } 15759 15760 /** 15761 * @hide 15762 */ 15763 @Override 15764 public void onResolveDrawables(int layoutDirection) { 15765 // No need to resolve twice 15766 if (mLastLayoutDirection == layoutDirection) { 15767 return; 15768 } 15769 mLastLayoutDirection = layoutDirection; 15770 15771 // Resolve drawables 15772 if (mDrawables != null) { 15773 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { 15774 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); 15775 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); 15776 applyCompoundDrawableTint(); 15777 } 15778 } 15779 } 15780 15781 /** 15782 * Prepares a drawable for display by propagating layout direction and 15783 * drawable state. 15784 * 15785 * @param dr the drawable to prepare 15786 */ 15787 private void prepareDrawableForDisplay(@Nullable Drawable dr) { 15788 if (dr == null) { 15789 return; 15790 } 15791 15792 dr.setLayoutDirection(getLayoutDirection()); 15793 15794 if (dr.isStateful()) { 15795 dr.setState(getDrawableState()); 15796 dr.jumpToCurrentState(); 15797 } 15798 } 15799 15800 /** 15801 * @hide 15802 */ 15803 protected void resetResolvedDrawables() { 15804 super.resetResolvedDrawables(); 15805 mLastLayoutDirection = -1; 15806 } 15807 15808 /** 15809 * @hide 15810 */ 15811 protected void viewClicked(InputMethodManager imm) { 15812 if (imm != null) { 15813 imm.viewClicked(this); 15814 } 15815 } 15816 15817 /** 15818 * Deletes the range of text [start, end[. 15819 * @hide 15820 */ 15821 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 15822 protected void deleteText_internal(int start, int end) { 15823 ((Editable) mText).delete(start, end); 15824 } 15825 15826 /** 15827 * Replaces the range of text [start, end[ by replacement text 15828 * @hide 15829 */ 15830 protected void replaceText_internal(int start, int end, CharSequence text) { 15831 ((Editable) mText).replace(start, end, text); 15832 } 15833 15834 /** 15835 * Sets a span on the specified range of text 15836 * @hide 15837 */ 15838 protected void setSpan_internal(Object span, int start, int end, int flags) { 15839 ((Editable) mText).setSpan(span, start, end, flags); 15840 } 15841 15842 /** 15843 * Moves the cursor to the specified offset position in text 15844 * @hide 15845 */ 15846 protected void setCursorPosition_internal(int start, int end) { 15847 Selection.setSelection(((Editable) mText), start, end); 15848 } 15849 15850 /** 15851 * An Editor should be created as soon as any of the editable-specific fields (grouped 15852 * inside the Editor object) is assigned to a non-default value. 15853 * This method will create the Editor if needed. 15854 * 15855 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 15856 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 15857 * Editor for backward compatibility, as soon as one of these fields is assigned. 15858 * 15859 * Also note that for performance reasons, the mEditor is created when needed, but not 15860 * reset when no more edit-specific fields are needed. 15861 */ 15862 @UnsupportedAppUsage 15863 private void createEditorIfNeeded() { 15864 if (mEditor == null) { 15865 mEditor = new Editor(this); 15866 } 15867 } 15868 15869 /** 15870 * @hide 15871 */ 15872 @Override 15873 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 15874 public CharSequence getIterableTextForAccessibility() { 15875 return mText; 15876 } 15877 15878 private void ensureIterableTextForAccessibilitySelectable() { 15879 if (!(mText instanceof Spannable)) { 15880 setText(mText, BufferType.SPANNABLE); 15881 if (getLayout() == null) { 15882 assumeLayout(); 15883 } 15884 } 15885 } 15886 15887 /** 15888 * @hide 15889 */ 15890 @Override 15891 public TextSegmentIterator getIteratorForGranularity(int granularity) { 15892 switch (granularity) { 15893 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 15894 Spannable text = (Spannable) getIterableTextForAccessibility(); 15895 if (!TextUtils.isEmpty(text) && getLayout() != null) { 15896 AccessibilityIterators.LineTextSegmentIterator iterator = 15897 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 15898 iterator.initialize(text, getLayout()); 15899 return iterator; 15900 } 15901 } break; 15902 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 15903 Spannable text = (Spannable) getIterableTextForAccessibility(); 15904 if (!TextUtils.isEmpty(text) && getLayout() != null) { 15905 AccessibilityIterators.PageTextSegmentIterator iterator = 15906 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 15907 iterator.initialize(this); 15908 return iterator; 15909 } 15910 } break; 15911 } 15912 return super.getIteratorForGranularity(granularity); 15913 } 15914 15915 /** 15916 * @hide 15917 */ 15918 @Override 15919 public int getAccessibilitySelectionStart() { 15920 return getSelectionStart(); 15921 } 15922 15923 /** 15924 * @hide 15925 */ 15926 public boolean isAccessibilitySelectionExtendable() { 15927 return true; 15928 } 15929 15930 /** 15931 * @hide 15932 */ 15933 public void prepareForExtendedAccessibilitySelection() { 15934 requestFocusOnNonEditableSelectableText(); 15935 } 15936 15937 /** 15938 * @hide 15939 */ 15940 @Override 15941 public int getAccessibilitySelectionEnd() { 15942 return getSelectionEnd(); 15943 } 15944 15945 /** 15946 * @hide 15947 */ 15948 @Override 15949 public void setAccessibilitySelection(int start, int end) { 15950 if (getAccessibilitySelectionStart() == start 15951 && getAccessibilitySelectionEnd() == end) { 15952 return; 15953 } 15954 CharSequence text = getIterableTextForAccessibility(); 15955 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 15956 Selection.setSelection((Spannable) text, start, end); 15957 } else { 15958 Selection.removeSelection((Spannable) text); 15959 } 15960 // Hide all selection controllers used for adjusting selection 15961 // since we are doing so explicitlty by other means and these 15962 // controllers interact with how selection behaves. 15963 if (mEditor != null) { 15964 mEditor.hideCursorAndSpanControllers(); 15965 mEditor.stopTextActionMode(); 15966 } 15967 } 15968 15969 /** @hide */ 15970 @Override 15971 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 15972 super.encodeProperties(stream); 15973 15974 TruncateAt ellipsize = getEllipsize(); 15975 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); 15976 stream.addProperty("text:textSize", getTextSize()); 15977 stream.addProperty("text:scaledTextSize", getScaledTextSize()); 15978 stream.addProperty("text:typefaceStyle", getTypefaceStyle()); 15979 stream.addProperty("text:selectionStart", getSelectionStart()); 15980 stream.addProperty("text:selectionEnd", getSelectionEnd()); 15981 stream.addProperty("text:curTextColor", mCurTextColor); 15982 stream.addUserProperty("text:text", mText == null ? null : mText.toString()); 15983 stream.addProperty("text:gravity", mGravity); 15984 } 15985 15986 /** 15987 * User interface state that is stored by TextView for implementing 15988 * {@link View#onSaveInstanceState}. 15989 */ 15990 public static class SavedState extends BaseSavedState { 15991 int selStart = -1; 15992 int selEnd = -1; 15993 @UnsupportedAppUsage 15994 CharSequence text; 15995 boolean frozenWithFocus; 15996 CharSequence error; 15997 ParcelableParcel editorState; // Optional state from Editor. 15998 15999 SavedState(Parcelable superState) { 16000 super(superState); 16001 } 16002 16003 @Override 16004 public void writeToParcel(Parcel out, int flags) { 16005 super.writeToParcel(out, flags); 16006 out.writeInt(selStart); 16007 out.writeInt(selEnd); 16008 out.writeInt(frozenWithFocus ? 1 : 0); 16009 TextUtils.writeToParcel(text, out, flags); 16010 16011 if (error == null) { 16012 out.writeInt(0); 16013 } else { 16014 out.writeInt(1); 16015 TextUtils.writeToParcel(error, out, flags); 16016 } 16017 16018 if (editorState == null) { 16019 out.writeInt(0); 16020 } else { 16021 out.writeInt(1); 16022 editorState.writeToParcel(out, flags); 16023 } 16024 } 16025 16026 @Override 16027 public String toString() { 16028 String str = "TextView.SavedState{" 16029 + Integer.toHexString(System.identityHashCode(this)) 16030 + " start=" + selStart + " end=" + selEnd; 16031 if (text != null) { 16032 str += " text=" + text; 16033 } 16034 return str + "}"; 16035 } 16036 16037 @SuppressWarnings("hiding") 16038 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR = 16039 new Parcelable.Creator<SavedState>() { 16040 public SavedState createFromParcel(Parcel in) { 16041 return new SavedState(in); 16042 } 16043 16044 public SavedState[] newArray(int size) { 16045 return new SavedState[size]; 16046 } 16047 }; 16048 16049 private SavedState(Parcel in) { 16050 super(in); 16051 selStart = in.readInt(); 16052 selEnd = in.readInt(); 16053 frozenWithFocus = (in.readInt() != 0); 16054 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 16055 16056 if (in.readInt() != 0) { 16057 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 16058 } 16059 16060 if (in.readInt() != 0) { 16061 editorState = ParcelableParcel.CREATOR.createFromParcel(in); 16062 } 16063 } 16064 } 16065 16066 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 16067 @NonNull 16068 private char[] mChars; 16069 private int mStart, mLength; 16070 16071 CharWrapper(@NonNull char[] chars, int start, int len) { 16072 mChars = chars; 16073 mStart = start; 16074 mLength = len; 16075 } 16076 16077 /* package */ void set(@NonNull char[] chars, int start, int len) { 16078 mChars = chars; 16079 mStart = start; 16080 mLength = len; 16081 } 16082 16083 public int length() { 16084 return mLength; 16085 } 16086 16087 public char charAt(int off) { 16088 return mChars[off + mStart]; 16089 } 16090 16091 @Override 16092 public String toString() { 16093 return new String(mChars, mStart, mLength); 16094 } 16095 16096 public CharSequence subSequence(int start, int end) { 16097 if (start < 0 || end < 0 || start > mLength || end > mLength) { 16098 throw new IndexOutOfBoundsException(start + ", " + end); 16099 } 16100 16101 return new String(mChars, start + mStart, end - start); 16102 } 16103 16104 public void getChars(int start, int end, char[] buf, int off) { 16105 if (start < 0 || end < 0 || start > mLength || end > mLength) { 16106 throw new IndexOutOfBoundsException(start + ", " + end); 16107 } 16108 16109 System.arraycopy(mChars, start + mStart, buf, off, end - start); 16110 } 16111 16112 @Override 16113 public void drawText(BaseCanvas c, int start, int end, 16114 float x, float y, Paint p) { 16115 c.drawText(mChars, start + mStart, end - start, x, y, p); 16116 } 16117 16118 @Override 16119 public void drawTextRun(BaseCanvas c, int start, int end, 16120 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 16121 int count = end - start; 16122 int contextCount = contextEnd - contextStart; 16123 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 16124 contextCount, x, y, isRtl, p); 16125 } 16126 16127 public float measureText(int start, int end, Paint p) { 16128 return p.measureText(mChars, start + mStart, end - start); 16129 } 16130 16131 public int getTextWidths(int start, int end, float[] widths, Paint p) { 16132 return p.getTextWidths(mChars, start + mStart, end - start, widths); 16133 } 16134 16135 public float getTextRunAdvances(int start, int end, int contextStart, 16136 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 16137 Paint p) { 16138 int count = end - start; 16139 int contextCount = contextEnd - contextStart; 16140 return p.getTextRunAdvances(mChars, start + mStart, count, 16141 contextStart + mStart, contextCount, isRtl, advances, 16142 advancesIndex); 16143 } 16144 16145 public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, 16146 int offset, int cursorOpt, Paint p) { 16147 int contextCount = contextEnd - contextStart; 16148 return p.getTextRunCursor(mChars, contextStart + mStart, 16149 contextCount, isRtl, offset + mStart, cursorOpt); 16150 } 16151 } 16152 16153 private static final class Marquee { 16154 // TODO: Add an option to configure this 16155 private static final float MARQUEE_DELTA_MAX = 0.07f; 16156 private static final int MARQUEE_DELAY = 1200; 16157 private static final int MARQUEE_DP_PER_SECOND = 30; 16158 16159 private static final byte MARQUEE_STOPPED = 0x0; 16160 private static final byte MARQUEE_STARTING = 0x1; 16161 private static final byte MARQUEE_RUNNING = 0x2; 16162 16163 private final WeakReference<TextView> mView; 16164 private final Choreographer mChoreographer; 16165 16166 private byte mStatus = MARQUEE_STOPPED; 16167 private final float mPixelsPerMs; 16168 private float mMaxScroll; 16169 private float mMaxFadeScroll; 16170 private float mGhostStart; 16171 private float mGhostOffset; 16172 private float mFadeStop; 16173 private int mRepeatLimit; 16174 16175 private float mScroll; 16176 private long mLastAnimationMs; 16177 16178 Marquee(TextView v) { 16179 final float density = v.getContext().getResources().getDisplayMetrics().density; 16180 mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f; 16181 mView = new WeakReference<TextView>(v); 16182 mChoreographer = Choreographer.getInstance(); 16183 } 16184 16185 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 16186 @Override 16187 public void doFrame(long frameTimeNanos) { 16188 tick(); 16189 } 16190 }; 16191 16192 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 16193 @Override 16194 public void doFrame(long frameTimeNanos) { 16195 mStatus = MARQUEE_RUNNING; 16196 mLastAnimationMs = mChoreographer.getFrameTime(); 16197 tick(); 16198 } 16199 }; 16200 16201 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 16202 @Override 16203 public void doFrame(long frameTimeNanos) { 16204 if (mStatus == MARQUEE_RUNNING) { 16205 if (mRepeatLimit >= 0) { 16206 mRepeatLimit--; 16207 } 16208 start(mRepeatLimit); 16209 } 16210 } 16211 }; 16212 16213 void tick() { 16214 if (mStatus != MARQUEE_RUNNING) { 16215 return; 16216 } 16217 16218 mChoreographer.removeFrameCallback(mTickCallback); 16219 16220 final TextView textView = mView.get(); 16221 if (textView != null && textView.isAggregatedVisible() 16222 && (textView.isFocused() || textView.isSelected())) { 16223 long currentMs = mChoreographer.getFrameTime(); 16224 long deltaMs = currentMs - mLastAnimationMs; 16225 mLastAnimationMs = currentMs; 16226 float deltaPx = deltaMs * mPixelsPerMs; 16227 mScroll += deltaPx; 16228 if (mScroll > mMaxScroll) { 16229 mScroll = mMaxScroll; 16230 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 16231 } else { 16232 mChoreographer.postFrameCallback(mTickCallback); 16233 } 16234 textView.invalidate(); 16235 } 16236 } 16237 16238 void stop() { 16239 mStatus = MARQUEE_STOPPED; 16240 mChoreographer.removeFrameCallback(mStartCallback); 16241 mChoreographer.removeFrameCallback(mRestartCallback); 16242 mChoreographer.removeFrameCallback(mTickCallback); 16243 resetScroll(); 16244 } 16245 16246 private void resetScroll() { 16247 mScroll = 0.0f; 16248 final TextView textView = mView.get(); 16249 if (textView != null) textView.invalidate(); 16250 } 16251 16252 void start(int repeatLimit) { 16253 if (repeatLimit == 0) { 16254 stop(); 16255 return; 16256 } 16257 mRepeatLimit = repeatLimit; 16258 final TextView textView = mView.get(); 16259 if (textView != null && textView.mLayout != null) { 16260 mStatus = MARQUEE_STARTING; 16261 mScroll = 0.0f; 16262 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() 16263 - textView.getCompoundPaddingRight(); 16264 final float lineWidth = textView.mLayout.getLineWidth(0); 16265 final float gap = textWidth / 3.0f; 16266 mGhostStart = lineWidth - textWidth + gap; 16267 mMaxScroll = mGhostStart + textWidth; 16268 mGhostOffset = lineWidth + gap; 16269 mFadeStop = lineWidth + textWidth / 6.0f; 16270 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 16271 16272 textView.invalidate(); 16273 mChoreographer.postFrameCallback(mStartCallback); 16274 } 16275 } 16276 16277 float getGhostOffset() { 16278 return mGhostOffset; 16279 } 16280 16281 float getScroll() { 16282 return mScroll; 16283 } 16284 16285 float getMaxFadeScroll() { 16286 return mMaxFadeScroll; 16287 } 16288 16289 boolean shouldDrawLeftFade() { 16290 return mScroll <= mFadeStop; 16291 } 16292 16293 boolean shouldDrawGhost() { 16294 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 16295 } 16296 16297 boolean isRunning() { 16298 return mStatus == MARQUEE_RUNNING; 16299 } 16300 16301 boolean isStopped() { 16302 return mStatus == MARQUEE_STOPPED; 16303 } 16304 } 16305 16306 private class ChangeWatcher implements TextWatcher, SpanWatcher { 16307 16308 private CharSequence mBeforeText; 16309 16310 public void beforeTextChanged(CharSequence buffer, int start, 16311 int before, int after) { 16312 if (DEBUG_EXTRACT) { 16313 Log.v(LOG_TAG, "beforeTextChanged start=" + start 16314 + " before=" + before + " after=" + after + ": " + buffer); 16315 } 16316 16317 if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) { 16318 mBeforeText = mTransformed.toString(); 16319 } 16320 16321 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 16322 } 16323 16324 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 16325 if (DEBUG_EXTRACT) { 16326 Log.v(LOG_TAG, "onTextChanged start=" + start 16327 + " before=" + before + " after=" + after + ": " + buffer); 16328 } 16329 TextView.this.handleTextChanged(buffer, start, before, after); 16330 16331 if (isVisibleToAccessibility()) { 16332 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 16333 mBeforeText = null; 16334 } 16335 } 16336 16337 public void afterTextChanged(Editable buffer) { 16338 if (DEBUG_EXTRACT) { 16339 Log.v(LOG_TAG, "afterTextChanged: " + buffer); 16340 } 16341 TextView.this.sendAfterTextChanged(buffer); 16342 16343 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 16344 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 16345 } 16346 } 16347 16348 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 16349 if (DEBUG_EXTRACT) { 16350 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 16351 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 16352 } 16353 TextView.this.spanChange(buf, what, s, st, e, en); 16354 } 16355 16356 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 16357 if (DEBUG_EXTRACT) { 16358 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf); 16359 } 16360 TextView.this.spanChange(buf, what, -1, s, -1, e); 16361 } 16362 16363 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 16364 if (DEBUG_EXTRACT) { 16365 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf); 16366 } 16367 TextView.this.spanChange(buf, what, s, -1, e, -1); 16368 } 16369 } 16370 16371 /** @hide */ 16372 @Override 16373 public void onInputConnectionOpenedInternal(@NonNull InputConnection ic, 16374 @NonNull EditorInfo editorInfo, @Nullable Handler handler) { 16375 if (mEditor != null) { 16376 mEditor.getDefaultOnReceiveContentListener().setInputConnectionInfo(this, ic, 16377 editorInfo); 16378 } 16379 } 16380 16381 /** @hide */ 16382 @Override 16383 public void onInputConnectionClosedInternal() { 16384 if (mEditor != null) { 16385 mEditor.getDefaultOnReceiveContentListener().clearInputConnectionInfo(); 16386 } 16387 } 16388 16389 /** 16390 * Default {@link TextView} implementation for receiving content. Apps wishing to provide 16391 * custom behavior should configure a listener via {@link #setOnReceiveContentListener}. 16392 * 16393 * <p>For non-editable TextViews the default behavior is a no-op (returns the passed-in 16394 * content without acting on it). 16395 * 16396 * <p>For editable TextViews the default behavior is to insert text into the view, coercing 16397 * non-text content to text as needed. The MIME types "text/plain" and "text/html" have 16398 * well-defined behavior for this, while other MIME types have reasonable fallback behavior 16399 * (see {@link ClipData.Item#coerceToStyledText}). 16400 * 16401 * @param payload The content to insert and related metadata. 16402 * 16403 * @return The portion of the passed-in content that was not handled (may be all, some, or none 16404 * of the passed-in content). 16405 */ 16406 @Nullable 16407 @Override 16408 public ContentInfo onReceiveContent(@NonNull ContentInfo payload) { 16409 if (mEditor != null) { 16410 return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload); 16411 } 16412 return payload; 16413 } 16414 16415 private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { 16416 if (msgFormat == null) { 16417 Log.d(LOG_TAG, location); 16418 } else { 16419 Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs)); 16420 } 16421 } 16422 16423 /** 16424 * Collects a {@link ViewTranslationRequest} which represents the content to be translated in 16425 * the view. 16426 * 16427 * <p>NOTE: When overriding the method, it should not collect a request to translate this 16428 * TextView if it is displaying a password. 16429 * 16430 * @param supportedFormats the supported translation format. The value could be {@link 16431 * android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}. 16432 * @param requestsCollector {@link Consumer} to receiver the {@link ViewTranslationRequest} 16433 * which contains the information to be translated. 16434 */ 16435 @Override 16436 public void onCreateViewTranslationRequest(@NonNull int[] supportedFormats, 16437 @NonNull Consumer<ViewTranslationRequest> requestsCollector) { 16438 if (supportedFormats == null || supportedFormats.length == 0) { 16439 if (UiTranslationController.DEBUG) { 16440 Log.w(LOG_TAG, "Do not provide the support translation formats."); 16441 } 16442 return; 16443 } 16444 ViewTranslationRequest.Builder requestBuilder = 16445 new ViewTranslationRequest.Builder(getAutofillId()); 16446 // Support Text translation 16447 if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) { 16448 if (mText == null || mText.length() == 0) { 16449 if (UiTranslationController.DEBUG) { 16450 Log.w(LOG_TAG, "Cannot create translation request for the empty text."); 16451 } 16452 return; 16453 } 16454 boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod(); 16455 if (isTextEditable() || isPassword) { 16456 Log.w(LOG_TAG, "Cannot create translation request. editable = " 16457 + isTextEditable() + ", isPassword = " + isPassword); 16458 return; 16459 } 16460 // TODO(b/176488462): apply the view's important for translation 16461 requestBuilder.setValue(ViewTranslationRequest.ID_TEXT, 16462 TranslationRequestValue.forText(mText)); 16463 if (!TextUtils.isEmpty(getContentDescription())) { 16464 requestBuilder.setValue(ViewTranslationRequest.ID_CONTENT_DESCRIPTION, 16465 TranslationRequestValue.forText(getContentDescription())); 16466 } 16467 } 16468 requestsCollector.accept(requestBuilder.build()); 16469 } 16470 } 16471