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 Drawables(Context context)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 setTextInternal(@ullable CharSequence text)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 */ setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)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 */ setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)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 */ setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)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 getAutoSizeTextType()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 getAutoSizeStepGranularity()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 getAutoSizeMinTextSize()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 getAutoSizeMaxTextSize()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 */ getAutoSizeTextAvailableSizes()2232 public int[] getAutoSizeTextAvailableSizes() { 2233 return mAutoSizeTextSizesInPx; 2234 } 2235 setupAutoSizeUniformPresetSizes(TypedArray textSizes)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 setupAutoSizeUniformPresetSizesConfiguration()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 */ validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)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 clearAutoSizeConfiguration()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. cleanupAutoSizePresetSizes(int[] presetValues)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 setupAutoSizeText()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 parseDimensionArray(TypedArray dimens)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 onActivityResult(int requestCode, int resultCode, @Nullable Intent data)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 */ setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) int weight)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 resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) int weight)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 setRelativeDrawablesIfNeeded(Drawable start, Drawable end)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 setEnabled(boolean enabled)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 */ setTypeface(@ullable Typeface tf, @Typeface.Style int style)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 */ getDefaultEditable()2549 protected boolean getDefaultEditable() { 2550 return false; 2551 } 2552 2553 /** 2554 * Subclasses override this to specify a default movement method. 2555 */ getDefaultMovementMethod()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 getText()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 */ length()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 */ getEditableText()2603 public Editable getEditableText() { 2604 return (mText instanceof Editable) ? (Editable) mText : null; 2605 } 2606 2607 /** 2608 * @hide 2609 */ 2610 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) getTransformed()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 getLineHeight()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 */ getLayout()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 getHintLayout()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 */ getUndoManager()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 getEditorForTesting()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 */ setUndoManager(UndoManager undoManager, String tag)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 */ getKeyListener()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 */ setKeyListener(KeyListener input)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 setInputTypeFromEditor()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 setKeyListenerOnly(KeyListener input)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 */ getMovementMethod()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 */ setMovementMethod(MovementMethod movement)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 fixFocusableAndClickableSettings()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 */ getTransformationMethod()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 */ setTransformationMethod(TransformationMethod method)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 */ setTransformationMethodInternal(@ullable TransformationMethod method, boolean updateText)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 */ getCompoundPaddingTop()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 */ getCompoundPaddingBottom()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 */ getCompoundPaddingLeft()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 */ getCompoundPaddingRight()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 */ getCompoundPaddingStart()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 */ getCompoundPaddingEnd()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 */ getExtendedPaddingTop()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 */ getExtendedPaddingBottom()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 */ getTotalPaddingLeft()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 */ getTotalPaddingRight()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 */ getTotalPaddingStart()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 */ getTotalPaddingEnd()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 */ getTotalPaddingTop()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 */ getTotalPaddingBottom()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 */ setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)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 setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)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 setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)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 setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)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 setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)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 setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)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 getCompoundDrawables()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 getCompoundDrawablesRelative()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 setCompoundDrawablePadding(int pad)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") getCompoundDrawablePadding()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 */ setCompoundDrawableTintList(@ullable ColorStateList tint)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") getCompoundDrawableTintList()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 */ setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)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 */ setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)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") getCompoundDrawableTintMode()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) getCompoundDrawableTintBlendMode()3704 public @Nullable BlendMode getCompoundDrawableTintBlendMode() { 3705 return mDrawables != null ? mDrawables.mBlendMode : null; 3706 } 3707 applyCompoundDrawableTint()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 setPadding(int left, int top, int right, int bottom)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 setPaddingRelative(int start, int top, int end, int bottom)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 */ setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)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 */ setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)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 getFirstBaselineToTopHeight()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 getLastBaselineToBottomHeight()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 }) getAutoLinkMask()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 setTextSelectHandle(@onNull Drawable textSelectHandle)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 setTextSelectHandle(@rawableRes int textSelectHandle)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 */ getTextSelectHandle()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 setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)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 setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)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 */ getTextSelectHandleLeft()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 setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)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 setTextSelectHandleRight(@rawableRes int textSelectHandleRight)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 */ getTextSelectHandleRight()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 */ setTextCursorDrawable(@ullable Drawable textCursorDrawable)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 */ setTextCursorDrawable(@rawableRes int textCursorDrawable)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 */ getTextCursorDrawable()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") setTextAppearance(@tyleRes int resId)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 setTextAppearance(Context context, @StyleRes int resId)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 toString()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 { sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)4247 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, 4248 com.android.internal.R.styleable.TextAppearance_textColorHighlight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor, com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor)4249 sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor, 4250 com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor); sAppearanceValues.put( com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor, com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor)4251 sAppearanceValues.put( 4252 com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor, 4253 com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)4254 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, 4255 com.android.internal.R.styleable.TextAppearance_textColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)4256 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, 4257 com.android.internal.R.styleable.TextAppearance_textColorHint); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)4258 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, 4259 com.android.internal.R.styleable.TextAppearance_textColorLink); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)4260 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, 4261 com.android.internal.R.styleable.TextAppearance_textSize); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)4262 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, 4263 com.android.internal.R.styleable.TextAppearance_textLocale); sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)4264 sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, 4265 com.android.internal.R.styleable.TextAppearance_typeface); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)4266 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, 4267 com.android.internal.R.styleable.TextAppearance_fontFamily); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)4268 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, 4269 com.android.internal.R.styleable.TextAppearance_textStyle); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)4270 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, 4271 com.android.internal.R.styleable.TextAppearance_textFontWeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)4272 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, 4273 com.android.internal.R.styleable.TextAppearance_textAllCaps); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)4274 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, 4275 com.android.internal.R.styleable.TextAppearance_shadowColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)4276 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, 4277 com.android.internal.R.styleable.TextAppearance_shadowDx); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)4278 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, 4279 com.android.internal.R.styleable.TextAppearance_shadowDy); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)4280 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, 4281 com.android.internal.R.styleable.TextAppearance_shadowRadius); sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)4282 sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, 4283 com.android.internal.R.styleable.TextAppearance_elegantTextHeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)4284 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, 4285 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)4286 sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, 4287 com.android.internal.R.styleable.TextAppearance_letterSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)4288 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, 4289 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)4290 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, 4291 com.android.internal.R.styleable.TextAppearance_fontVariationSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, com.android.internal.R.styleable.TextAppearance_lineBreakStyle)4292 sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, 4293 com.android.internal.R.styleable.TextAppearance_lineBreakStyle); sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle, com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle)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 */ readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)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 applyTextAppearance(TextAppearanceAttributes attributes)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 */ updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified, boolean isLineBreakWordStyleSpecified, @LineBreakConfig.LineBreakStyle int lineBreakStyle, @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)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 getTextLocale()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) getTextLocales()4557 public LocaleList getTextLocales() { 4558 return mTextPaint.getTextLocales(); 4559 } 4560 changeListenerLocaleTo(@ullable Locale locale)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 */ setTextLocale(@onNull Locale locale)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 */ setTextLocales(@onNull @izemin = 1) LocaleList locales)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 onConfigurationChanged(Configuration newConfig)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") getTextSize()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") getScaledTextSize()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 }) getTypefaceStyle()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 setTextSize(float size)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 */ setTextSize(int unit, float size)4718 public void setTextSize(int unit, float size) { 4719 if (!isAutoSizeEnabled()) { 4720 setTextSizeInternal(unit, size, true /* shouldRequestLayout */); 4721 } 4722 } 4723 4724 @NonNull getDisplayMetricsOrSystem()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 setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)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) setRawTextSize(float size, boolean shouldRequestLayout)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 */ getTextSizeUnit()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 getTextScaleX()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 setTextScaleX(float size)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 */ setTypeface(@ullable Typeface tf)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 getTypeface()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 */ setElegantTextHeight(boolean elegant)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 */ setFallbackLineSpacing(boolean enabled)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) setUseBoundsForWidth(boolean useBoundsForWidth)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) getUseBoundsForWidth()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) setShiftDrawingOffsetForStartOverhang(boolean shiftDrawingOffsetForStartOverhang)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) getShiftDrawingOffsetForStartOverhang()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) setMinimumFontMetrics(@ullable Paint.FontMetrics minimumFontMetrics)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) getMinimumFontMetrics()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) isLocalePreferredLineHeightForMinimumUsed()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) setLocalePreferredLineHeightForMinimumUsed(boolean flag)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 isFallbackLineSpacing()5106 public boolean isFallbackLineSpacing() { 5107 return mUseFallbackLineSpacing != FALLBACK_LINE_SPACING_NONE; 5108 } 5109 isFallbackLineSpacingForBoringLayout()5110 private boolean isFallbackLineSpacingForBoringLayout() { 5111 return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL; 5112 } 5113 5114 // Package privte for accessing from Editor.java isFallbackLineSpacingForStaticLayout()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 isElegantTextHeight()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 getLetterSpacing()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 setLetterSpacing(float letterSpacing)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 getFontFeatureSettings()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 getFontVariationSettings()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 */ setBreakStrategy(@ayout.BreakStrategy int breakStrategy)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 getBreakStrategy()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 */ setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)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 getHyphenationFrequency()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 */ setLineBreakStyle(@ineBreakConfig.LineBreakStyle int lineBreakStyle)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 */ setLineBreakWordStyle(@ineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)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 */ getLineBreakStyle()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 */ getLineBreakWordStyle()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 */ getTextMetricsParams()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 */ setTextMetricsParams(@onNull PrecomputedText.Params params)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 setJustificationMode(@ayout.JustificationMode int justificationMode)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 }) getJustificationMode()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 setFontFeatureSettings(@ullable String fontFeatureSettings)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 setFontVariationSettings(@ullable String fontVariationSettings)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 setTextColor(@olorInt int color)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 setTextColor(ColorStateList colors)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") getTextColors()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 getCurrentTextColor()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 setHighlightColor(@olorInt int color)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 getHighlightColor()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 setShowSoftInputOnFocus(boolean show)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 */ getShowSoftInputOnFocus()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 */ setShadowLayer(float radius, float dx, float dy, int color)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 getShadowRadius()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 getShadowDx()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 getShadowDy()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 getShadowColor()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 */ getPaint()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 setAutoLinkMask(int mask)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 setLinksClickable(boolean whether)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 getLinksClickable()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 */ getUrls()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 setHintTextColor(@olorInt int color)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 */ setHintTextColor(ColorStateList colors)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") getHintTextColors()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 getCurrentHintTextColor()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 setLinkTextColor(@olorInt int color)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 */ setLinkTextColor(ColorStateList colors)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") getLinkTextColors()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 setGravity(int gravity)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) getGravity()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 */ getPaintFlags()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 setPaintFlags(int flags)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 */ setHorizontallyScrolling(boolean whether)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") isHorizontallyScrollable()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) getHorizontallyScrolling()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 setMinLines(int minLines)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 getMinLines()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 setMinHeight(int minPixels)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 */ getMinHeight()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 setMaxLines(int maxLines)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 getMaxLines()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 setMaxHeight(int maxPixels)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 getMaxHeight()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 setLines(int lines)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 setHeight(int pixels)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 setMinEms(int minEms)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 getMinEms()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 setMinWidth(int minPixels)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 getMinWidth()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 setMaxEms(int maxEms)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 getMaxEms()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 setMaxWidth(int maxPixels)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 getMaxWidth()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 setEms(int ems)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 setWidth(int pixels)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 */ setLineSpacing(float add, float mult)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 getLineSpacingMultiplier()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 getLineSpacingExtra()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 setLineHeight(@x @ntRangefrom = 0) int lineHeight)6500 public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { 6501 setLineHeightPx(lineHeight); 6502 } 6503 setLineHeightPx(@x @loatRangefrom = 0) float lineHeight)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 setLineHeight( @ypedValue.ComplexDimensionUnit int unit, @FloatRange(from = 0) float lineHeight )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 maybeRecalculateLineHeight()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 */ setHighlights(@ullable Highlights highlights)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 getHighlights()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 */ setSearchResultHighlights(@ullable int... ranges)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 getSearchResultHighlights()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 */ setFocusedSearchResultIndex(int index)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 */ getFocusedSearchResultIndex()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 */ setSearchResultHighlightColor(@olorInt int color)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 getSearchResultHighlightColor()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 */ setFocusedSearchResultHighlightColor(@olorInt int color)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 getFocusedSearchResultHighlightColor()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 */ setSelectGesturePreviewHighlight(int start, int end)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 */ setDeleteGesturePreviewHighlight(int start, int end)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 setGesturePreviewHighlight(int start, int end, int color)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 clearGesturePreviewHighlight()6891 private void clearGesturePreviewHighlight() { 6892 mGesturePreviewHighlightStart = -1; 6893 mGesturePreviewHighlightEnd = -1; 6894 mHighlightPathsBogus = true; 6895 invalidate(); 6896 } 6897 hasGesturePreviewHighlight()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 */ append(CharSequence text)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 */ append(CharSequence text, int start, int end)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 updateTextColors()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 drawableStateChanged()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 drawableHotspotChanged(float x, float y)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 onSaveInstanceState()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 removeMisspelledSpans(Spannable spannable)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 onRestoreInstanceState(Parcelable state)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 setFreezesText(boolean freezesText)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 getFreezesText()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 */ setEditableFactory(Editable.Factory factory)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 */ setSpannableFactory(Spannable.Factory factory)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") setText(CharSequence text)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 setTextAsync(@ullable CharSequence text)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 setTextKeepState(CharSequence text)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 */ setText(CharSequence text, BufferType type)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 setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)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 */ setText(@onNull char[] text, int start, int len)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 */ setTextKeepState(CharSequence text, BufferType type)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 setText(@tringRes int resid)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 */ setText(@tringRes int resid, BufferType type)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 setHint(CharSequence hint)7624 public final void setHint(CharSequence hint) { 7625 setHintInternal(hint); 7626 7627 if (mEditor != null && isInputMethodTarget()) { 7628 mEditor.reportExtractedText(); 7629 } 7630 } 7631 setHintInternal(CharSequence hint)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 setHint(@tringRes int resid)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 getHint()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 */ hideHint()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 isSingleLine()7694 public boolean isSingleLine() { 7695 return mSingleLine; 7696 } 7697 isMultilineInputType(int type)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 */ removeSuggestionSpans(CharSequence text)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 */ setInputType(int type)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 */ hasPasswordTransformationMethod()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 */ isAnyPasswordInputType()7813 public boolean isAnyPasswordInputType() { 7814 final int inputType = getInputType(); 7815 return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType); 7816 } 7817 isPasswordInputType(int inputType)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 isVisiblePasswordInputType(int inputType)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 */ setRawInputType(int type)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 getAutofillHints()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 getCustomLocaleForKeyListenerOrNull()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 setInputType(int type, boolean direct)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 }) getInputType()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 */ setImeOptions(int imeOptions)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 }) getImeOptions()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 */ setImeActionLabel(CharSequence label, int actionId)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 getImeActionLabel()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 getImeActionId()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 */ setOnEditorActionListener(OnEditorActionListener l)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 */ onEditorAction(int actionCode)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 */ setPrivateImeOptions(String type)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 getPrivateImeOptions()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 */ setInputExtras(@mlRes int xmlResId)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 */ getInputExtras(boolean create)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 */ setImeHintLocales(@ullable LocaleList hintLocales)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 getImeHintLocales()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 */ getError()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 setError(CharSequence error)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 */ setError(CharSequence error, Drawable icon)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 setFrame(int l, int t, int r, int b)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 restartMarqueeIfNeeded()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 */ setFilters(InputFilter[] filters)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 */ setFilters(Editable e, InputFilter[] filters)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 */ getFilters()8541 public InputFilter[] getFilters() { 8542 return mFilters; 8543 } 8544 8545 ///////////////////////////////////////////////////////////////////////// 8546 getBoxHeight(Layout l)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 getVerticalOffset(boolean forceNormal)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 getBottomVerticalOffset(boolean forceNormal)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 invalidateCursorPath()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 invalidateCursor()8645 void invalidateCursor() { 8646 int where = getSelectionEnd(); 8647 8648 invalidateCursor(where, where, where); 8649 } 8650 invalidateCursor(int a, int b, int c)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 */ invalidateRegion(int start, int end, boolean invalidateCursor)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 registerForPreDraw()8718 private void registerForPreDraw() { 8719 if (!mPreDrawRegistered) { 8720 getViewTreeObserver().addOnPreDrawListener(this); 8721 mPreDrawRegistered = true; 8722 } 8723 } 8724 unregisterForPreDraw()8725 private void unregisterForPreDraw() { 8726 getViewTreeObserver().removeOnPreDrawListener(this); 8727 mPreDrawRegistered = false; 8728 mPreDrawListenerDetached = false; 8729 } 8730 8731 /** 8732 * {@inheritDoc} 8733 */ 8734 @Override onPreDraw()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 onAttachedToWindow()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 onDetachedFromWindowInternal()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 onScreenStateChanged(int screenState)8809 public void onScreenStateChanged(int screenState) { 8810 super.onScreenStateChanged(screenState); 8811 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 8812 } 8813 8814 @Override isPaddingOffsetRequired()8815 protected boolean isPaddingOffsetRequired() { 8816 return mShadowRadius != 0 || mDrawables != null; 8817 } 8818 8819 @Override getLeftPaddingOffset()8820 protected int getLeftPaddingOffset() { 8821 return getCompoundPaddingLeft() - mPaddingLeft 8822 + (int) Math.min(0, mShadowDx - mShadowRadius); 8823 } 8824 8825 @Override getTopPaddingOffset()8826 protected int getTopPaddingOffset() { 8827 return (int) Math.min(0, mShadowDy - mShadowRadius); 8828 } 8829 8830 @Override getBottomPaddingOffset()8831 protected int getBottomPaddingOffset() { 8832 return (int) Math.max(0, mShadowDy + mShadowRadius); 8833 } 8834 8835 @Override getRightPaddingOffset()8836 protected int getRightPaddingOffset() { 8837 return -(getCompoundPaddingRight() - mPaddingRight) 8838 + (int) Math.max(0, mShadowDx + mShadowRadius); 8839 } 8840 8841 @Override verifyDrawable(@onNull Drawable who)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 jumpDrawablesToCurrentState()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 invalidateDrawable(@onNull Drawable drawable)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 hasOverlappingRendering()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") isTextSelectable()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 */ setTextIsSelectable(boolean selectable)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 onCreateDrawableState(int extraSpace)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 maybeUpdateHighlightPaths()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 addSearchHighlightPaths()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) getUpdatedHighlightPath()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 */ getHorizontalOffsetForDrawables()9196 public int getHorizontalOffsetForDrawables() { 9197 return 0; 9198 } 9199 9200 @Override onDraw(Canvas canvas)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 getFocusedRect(Rect r)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 */ getLineCount()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 */ getLineBounds(int line, Rect bounds)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 getBaseline()9466 public int getBaseline() { 9467 if (mLayout == null) { 9468 return super.getBaseline(); 9469 } 9470 9471 return getBaselineOffset() + mLayout.getLineBaseline(0); 9472 } 9473 getBaselineOffset()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 getFadeTop(boolean offsetRequired)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 getFadeHeight(boolean offsetRequired)9508 protected int getFadeHeight(boolean offsetRequired) { 9509 return mLayout != null ? mLayout.getHeight() : 0; 9510 } 9511 9512 @Override onResolvePointerIcon(MotionEvent event, int pointerIndex)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 onKeyPreIme(int keyCode, KeyEvent event)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 */ handleBackInTextActionModeIfNeeded(KeyEvent event)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 onKeyDown(int keyCode, KeyEvent event)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 onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)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 */ shouldAdvanceFocusOnEnter()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 isDirectionalNavigationKey(int keyCode)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 doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)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 */ resetErrorChangedFlag()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 */ hideErrorIfUnchanged()9882 public void hideErrorIfUnchanged() { 9883 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 9884 setError(null, null); 9885 } 9886 } 9887 9888 @Override onKeyUp(int keyCode, KeyEvent event)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 getActionIdForEnterEvent()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 onCheckIsTextEditor()10005 public boolean onCheckIsTextEditor() { 10006 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 10007 } 10008 hasEditorInFocusSearchDirection(@ocusRealDirection int direction)10009 private boolean hasEditorInFocusSearchDirection(@FocusRealDirection int direction) { 10010 final View nextView = focusSearch(direction); 10011 return nextView != null && nextView.onCheckIsTextEditor(); 10012 } 10013 10014 @Override onCreateInputConnection(EditorInfo outAttrs)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 */ onRequestCursorUpdatesInternal( @nputConnection.CursorUpdateMode int cursorUpdateMode, @InputConnection.CursorUpdateFilter int cursorUpdateFilter)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 */ extractText(ExtractedTextRequest request, ExtractedText outText)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 */ removeParcelableSpans(Spannable spannable, int start, int end)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 */ setExtractedText(ExtractedText text)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 */ setExtracting(ExtractedTextRequest req)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 */ onCommitCompletion(CompletionInfo text)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 */ onCommitCorrection(CorrectionInfo info)10266 public void onCommitCorrection(CorrectionInfo info) { 10267 if (mEditor != null) mEditor.onCommitCorrection(info); 10268 } 10269 beginBatchEdit()10270 public void beginBatchEdit() { 10271 if (mEditor != null) mEditor.beginBatchEdit(); 10272 } 10273 endBatchEdit()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 */ onBeginBatchEdit()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 */ onEndBatchEdit()10290 public void onEndBatchEdit() { 10291 // intentionally empty 10292 } 10293 10294 /** @hide */ onPerformSpellCheck()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 */ onPrivateIMECommand(String action, Bundle data)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 */ isOffsetMappingAvailable()10319 public boolean isOffsetMappingAvailable() { 10320 return mTransformation != null && mTransformed instanceof OffsetMapping; 10321 } 10322 10323 /** @hide */ previewHandwritingGesture( @onNull PreviewableHandwritingGesture gesture, @Nullable CancellationSignal cancellationSignal)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 */ performHandwritingSelectGesture(@onNull SelectGesture gesture)10347 public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) { 10348 return performHandwritingSelectGesture(gesture, /* isPreview= */ false); 10349 } 10350 performHandwritingSelectGesture(@onNull SelectGesture gesture, boolean isPreview)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 performHandwritingSelectGesture(int[] range, boolean isPreview)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 */ performHandwritingSelectRangeGesture(@onNull SelectRangeGesture gesture)10375 public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) { 10376 return performHandwritingSelectRangeGesture(gesture, /* isPreview= */ false); 10377 } 10378 performHandwritingSelectRangeGesture( @onNull SelectRangeGesture gesture, boolean isPreview)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 */ performHandwritingDeleteGesture(@onNull DeleteGesture gesture)10403 public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) { 10404 return performHandwritingDeleteGesture(gesture, /* isPreview= */ false); 10405 } 10406 performHandwritingDeleteGesture(@onNull DeleteGesture gesture, boolean isPreview)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 performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview)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 */ performHandwritingDeleteRangeGesture(@onNull DeleteRangeGesture gesture)10435 public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) { 10436 return performHandwritingDeleteRangeGesture(gesture, /* isPreview= */ false); 10437 } 10438 performHandwritingDeleteRangeGesture( @onNull DeleteRangeGesture gesture, boolean isPreview)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 adjustHandwritingDeleteGestureRange(int[] range)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 onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 */ autoSizeText()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 */ findLargestTextSizeWhichFits(RectF availableSpace)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 suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)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 getDesiredHeight()11531 private int getDesiredHeight() { 11532 return Math.max( 11533 getDesiredHeight(mLayout, true), 11534 getDesiredHeight(mHintLayout, mEllipsize != null)); 11535 } 11536 getDesiredHeight(Layout layout, boolean cap)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 */ checkForResize()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 checkForRelayout()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 onLayout(boolean changed, int left, int top, int right, int bottom)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 isShowingHint()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 bringTextIntoView()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 */ bringPointIntoView(int offset)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 */ bringPointIntoView(@ntRangefrom = 0) int offset, boolean requestRectWithoutFocus)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 */ moveCursorToVisibleOffset()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 computeScroll()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 getInterestingRect(Rect r, int line)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 convertFromViewportToContentCoordinates(Rect r)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 convertFromScreenToContentCoordinates(PointF point)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 convertFromScreenToContentCoordinates(RectF rect)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 viewportToContentHorizontalOffset()12110 int viewportToContentHorizontalOffset() { 12111 return getCompoundPaddingLeft() - mScrollX; 12112 } 12113 12114 @UnsupportedAppUsage viewportToContentVerticalOffset()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 debug(int depth)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") getSelectionStart()12149 public int getSelectionStart() { 12150 return Selection.getSelectionStart(getText()); 12151 } 12152 12153 /** 12154 * Convenience for {@link Selection#getSelectionEnd}. 12155 */ 12156 @ViewDebug.ExportedProperty(category = "text") getSelectionEnd()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 */ getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer)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 getSelectionStartTransformed()12178 int getSelectionStartTransformed() { 12179 final int start = getSelectionStart(); 12180 if (start < 0) return start; 12181 return originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR); 12182 } 12183 getSelectionEndTransformed()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 */ hasSelection()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 getSelectedText()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 */ setSingleLine()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 setAllCaps(boolean allCaps)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") isAllCaps()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 setSingleLine(boolean singleLine)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 */ setInputTypeSingleLine(boolean singleLine)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 applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines, boolean changeMaxLength)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 */ setEllipsize(TextUtils.TruncateAt where)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 */ setMarqueeRepeatLimit(int marqueeLimit)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 getMarqueeRepeatLimit()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 getEllipsize()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 setSelectAllOnFocus(boolean selectAllOnFocus)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 setCursorVisible(boolean visible)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 */ setImeConsumesInput(boolean imeConsumesInput)12494 public void setImeConsumesInput(boolean imeConsumesInput) { 12495 mImeIsConsumingInput = imeConsumesInput; 12496 updateCursorVisibleInternal(); 12497 } 12498 updateCursorVisibleInternal()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 isCursorVisible()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 */ isCursorVisibleFromAttr()12537 public boolean isCursorVisibleFromAttr() { 12538 return mCursorVisibleFromAttr; 12539 } 12540 canMarquee()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) startMarquee()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 */ stopMarquee()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) startStopMarquee(boolean start)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 */ onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)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 onSelectionChanged(int selStart, int selEnd)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 */ addTextChangedListener(TextWatcher watcher)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 */ removeTextChangedListener(TextWatcher watcher)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 sendBeforeTextChanged(CharSequence text, int start, int before, int after)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 removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)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 removeAdjacentSuggestionSpans(final int pos)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 */ sendOnTextChanged(CharSequence text, int start, int before, int after)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 */ sendAfterTextChanged(Editable text)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 */ notifyListeningManagersAfterTextChanged()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 */ notifyContentCaptureTextChanged()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 isAutofillable()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 updateAfterEdit()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 */ handleTextChanged(CharSequence buffer, int start, int before, int after)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 */ spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)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 onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)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 onWindowFocusChanged(boolean hasWindowFocus)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 onVisibilityChanged(View changedView, int visibility)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 onVisibilityAggregated(boolean isVisible)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 */ clearComposingText()13040 public void clearComposingText() { 13041 if (mText instanceof Spannable) { 13042 BaseInputConnection.removeComposingSpans(mSpannable); 13043 } 13044 } 13045 13046 @Override setSelected(boolean selected)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 */ isFromPrimePointer(MotionEvent event, boolean fromHandleView)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 onTouchEvent(MotionEvent event)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 */ shouldStartHandwritingForEndOfLineTap(MotionEvent actionUpEvent)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 */ showUIForTouchScreen()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 */ showAutofillDialog()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 onGenericMotionEvent(MotionEvent event)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 onCreateContextMenu(ContextMenu menu)13283 protected void onCreateContextMenu(ContextMenu menu) { 13284 if (mEditor != null) { 13285 mEditor.onCreateContextMenu(menu); 13286 } 13287 } 13288 13289 @Override showContextMenu()13290 public boolean showContextMenu() { 13291 if (mEditor != null) { 13292 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); 13293 } 13294 return super.showContextMenu(); 13295 } 13296 13297 @Override showContextMenu(float x, float y)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 isTextEditable()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 isTextAutofillable()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 */ didTouchFocusSelect()13329 public boolean didTouchFocusSelect() { 13330 return mEditor != null && mEditor.mTouchFocusSelected; 13331 } 13332 13333 @Override cancelLongPress()13334 public void cancelLongPress() { 13335 super.cancelLongPress(); 13336 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 13337 } 13338 13339 @Override onTrackballEvent(MotionEvent event)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 */ setScroller(Scroller s)13355 public void setScroller(Scroller s) { 13356 mScroller = s; 13357 } 13358 13359 @Override getLeftFadingEdgeStrength()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 getRightFadingEdgeStrength()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) getHorizontalFadingEdgeStrength(float position1, float position2)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 isMarqueeFadeEnabled()13409 private boolean isMarqueeFadeEnabled() { 13410 return mEllipsize == TextUtils.TruncateAt.MARQUEE 13411 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 13412 } 13413 13414 @Override computeHorizontalScrollRange()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 computeVerticalScrollRange()13425 protected int computeVerticalScrollRange() { 13426 if (mLayout != null) { 13427 return mLayout.getHeight(); 13428 } 13429 return super.computeVerticalScrollRange(); 13430 } 13431 13432 @Override computeVerticalScrollExtent()13433 protected int computeVerticalScrollExtent() { 13434 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 13435 } 13436 13437 @Override findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)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 */ getTextColors(Context context, TypedArray attrs)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 */ getTextColor(Context context, TypedArray attrs, int def)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 onKeyShortcut(int keyCode, KeyEvent event)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 */ canSelectText()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 */ textCanBeSelected()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 getTextServicesLocale(boolean allowNullLocale)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) setTextOperationUser(@ullable UserHandle user)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 isAutoHandwritingEnabled()13632 public boolean isAutoHandwritingEnabled() { 13633 return super.isAutoHandwritingEnabled() && !isAnyPasswordInputType(); 13634 } 13635 13636 /** @hide */ 13637 @Override shouldTrackHandwritingArea()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 isStylusHandwritingAvailable()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 getTextServicesManagerForUser()13656 final TextServicesManager getTextServicesManagerForUser() { 13657 return getServiceManagerForUser("android", TextServicesManager.class); 13658 } 13659 13660 @Nullable getClipboardManagerForUser()13661 final ClipboardManager getClipboardManagerForUser() { 13662 return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class); 13663 } 13664 13665 @Nullable getTextClassificationManagerForUser()13666 final TextClassificationManager getTextClassificationManagerForUser() { 13667 return getServiceManagerForUser( 13668 getContext().getPackageName(), TextClassificationManager.class); 13669 } 13670 13671 @Nullable getServiceManagerForUser(String packageName, Class<T> managerClazz)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 */ startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)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 getTextServicesLocale()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 */ isInExtractedMode()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 */ isAutoSizeEnabled()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 */ supportsAutoSizeText()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 */ getSpellCheckerLocale()13755 public Locale getSpellCheckerLocale() { 13756 return getTextServicesLocale(true /* allowNullLocale */); 13757 } 13758 updateTextServicesLocaleAsync()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 updateTextServicesLocaleLocked()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 onLocaleChanged()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 */ getWordIterator()13796 public WordIterator getWordIterator() { 13797 if (mEditor != null) { 13798 return mEditor.getWordIterator(); 13799 } else { 13800 return null; 13801 } 13802 } 13803 13804 /** @hide */ 13805 @Override onPopulateAccessibilityEventInternal(AccessibilityEvent event)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 getAccessibilityClassName()13823 public CharSequence getAccessibilityClassName() { 13824 return TextView.class.getName(); 13825 } 13826 13827 /** @hide */ 13828 @Override onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)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 canRequestAutofill()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 requestAutofill()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 autofill(AutofillValue value)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 getAutofillType()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 getAutofillValue()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 onInitializeAccessibilityEventInternal(AccessibilityEvent event)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 onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)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 addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)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 */ getViewVisibleRect(Rect rect)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 */ getContentVisibleRect(Rect rect)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 */ populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)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 */ getCharacterBounds(int start, int end, float layoutLeft, float layoutTop)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 getCursorAnchorInfo(@nputConnection.CursorUpdateFilter int filter, @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder, @NonNull Matrix viewToScreenMatrix)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 */ getTextBoundsInfo(@onNull RectF bounds)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 */ isPositionVisible(final float positionX, final float positionY)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 performAccessibilityActionInternal(int action, Bundle arguments)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 performAccessibilityActionClick(Bundle arguments)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 requestFocusOnNonEditableSelectableText()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 hasSpannableText()14885 private boolean hasSpannableText() { 14886 return mText != null && mText instanceof Spannable; 14887 } 14888 14889 /** @hide */ 14890 @Override sendAccessibilityEventInternal(int eventType)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 sendAccessibilityEventUnchecked(AccessibilityEvent event)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) getTextForAccessibility()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 isVisibleToAccessibility()14930 boolean isVisibleToAccessibility() { 14931 return AccessibilityManager.getInstance(mContext).isEnabled() 14932 && (isFocused() || (isSelected() && isShown())); 14933 } 14934 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)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 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int toIndex)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 getInputMethodManager()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 */ isInputMethodTarget()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 */ onTextContextMenuItem(int id)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) getTransformedText(int start, int end)15079 CharSequence getTransformedText(int start, int end) { 15080 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 15081 } 15082 15083 @Override performLongClick()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 onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)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 */ isSuggestionsEnabled()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 */ setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)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 */ getCustomSelectionActionModeCallback()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 */ setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)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 */ getCustomInsertionActionModeCallback()15241 public ActionMode.Callback getCustomInsertionActionModeCallback() { 15242 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; 15243 } 15244 15245 /** 15246 * Sets the {@link TextClassifier} for this TextView. 15247 */ setTextClassifier(@ullable TextClassifier textClassifier)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 getTextClassifier()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 getTextClassificationSession()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 getTextClassificationContext()15308 TextClassificationContext getTextClassificationContext() { 15309 return mTextClassificationContext; 15310 } 15311 15312 /** 15313 * Returns true if this TextView uses a no-op TextClassifier. 15314 */ usesNoOpTextClassifier()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 */ requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)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 */ handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)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 stopTextActionMode()15392 protected void stopTextActionMode() { 15393 if (mEditor != null) { 15394 mEditor.stopTextActionMode(); 15395 } 15396 } 15397 15398 /** @hide */ hideFloatingToolbar(int durationMs)15399 public void hideFloatingToolbar(int durationMs) { 15400 if (mEditor != null) { 15401 mEditor.hideFloatingToolbar(durationMs); 15402 } 15403 } 15404 canUndo()15405 boolean canUndo() { 15406 return mEditor != null && mEditor.canUndo(); 15407 } 15408 canRedo()15409 boolean canRedo() { 15410 return mEditor != null && mEditor.canRedo(); 15411 } 15412 canCut()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 canCopy()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 canReplace()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 canShare()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 isDeviceProvisioned()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 canPaste()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 canPasteAsPlainText()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 canProcessText()15490 boolean canProcessText() { 15491 if (getId() == View.NO_ID) { 15492 return false; 15493 } 15494 return canShare(); 15495 } 15496 canSelectAllText()15497 boolean canSelectAllText() { 15498 return canSelectText() && !hasPasswordTransformationMethod() 15499 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); 15500 } 15501 selectAllText()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 paste(boolean withFormatting)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 shareSelectedText()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 setPrimaryClip(ClipData clip)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 */ getOffsetForPosition(float x, float y)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 convertToLocalHorizontalCoordinate(float x)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) getLineAtCoordinate(float y)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 getLineAtCoordinateUnclamped(float y)15586 int getLineAtCoordinateUnclamped(float y) { 15587 y -= getTotalPaddingTop(); 15588 y += getScrollY(); 15589 return getLayout().getLineForVertical((int) y); 15590 } 15591 getOffsetAtCoordinate(int line, float x)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 */ transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy)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 */ originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy)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 onDragEvent(DragEvent event)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 isInBatchEditMode()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 onRtlPropertiesChanged(int layoutDirection)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 */ getTextDirectionHeuristic()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 onResolveDrawables(int layoutDirection)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 */ prepareDrawableForDisplay(@ullable Drawable dr)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 */ resetResolvedDrawables()15803 protected void resetResolvedDrawables() { 15804 super.resetResolvedDrawables(); 15805 mLastLayoutDirection = -1; 15806 } 15807 15808 /** 15809 * @hide 15810 */ viewClicked(InputMethodManager imm)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) deleteText_internal(int start, int end)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 */ replaceText_internal(int start, int end, CharSequence text)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 */ setSpan_internal(Object span, int start, int end, int flags)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 */ setCursorPosition_internal(int start, int end)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 createEditorIfNeeded()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) getIterableTextForAccessibility()15874 public CharSequence getIterableTextForAccessibility() { 15875 return mText; 15876 } 15877 ensureIterableTextForAccessibilitySelectable()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 getIteratorForGranularity(int granularity)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 getAccessibilitySelectionStart()15919 public int getAccessibilitySelectionStart() { 15920 return getSelectionStart(); 15921 } 15922 15923 /** 15924 * @hide 15925 */ isAccessibilitySelectionExtendable()15926 public boolean isAccessibilitySelectionExtendable() { 15927 return true; 15928 } 15929 15930 /** 15931 * @hide 15932 */ prepareForExtendedAccessibilitySelection()15933 public void prepareForExtendedAccessibilitySelection() { 15934 requestFocusOnNonEditableSelectableText(); 15935 } 15936 15937 /** 15938 * @hide 15939 */ 15940 @Override getAccessibilitySelectionEnd()15941 public int getAccessibilitySelectionEnd() { 15942 return getSelectionEnd(); 15943 } 15944 15945 /** 15946 * @hide 15947 */ 15948 @Override setAccessibilitySelection(int start, int end)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 encodeProperties(@onNull ViewHierarchyEncoder stream)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 SavedState(Parcelable superState)15999 SavedState(Parcelable superState) { 16000 super(superState); 16001 } 16002 16003 @Override writeToParcel(Parcel out, int flags)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 toString()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 SavedState(Parcel in)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 CharWrapper(@onNull char[] chars, int start, int len)16071 CharWrapper(@NonNull char[] chars, int start, int len) { 16072 mChars = chars; 16073 mStart = start; 16074 mLength = len; 16075 } 16076 set(@onNull char[] chars, int start, int len)16077 /* package */ void set(@NonNull char[] chars, int start, int len) { 16078 mChars = chars; 16079 mStart = start; 16080 mLength = len; 16081 } 16082 length()16083 public int length() { 16084 return mLength; 16085 } 16086 charAt(int off)16087 public char charAt(int off) { 16088 return mChars[off + mStart]; 16089 } 16090 16091 @Override toString()16092 public String toString() { 16093 return new String(mChars, mStart, mLength); 16094 } 16095 subSequence(int start, int end)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 getChars(int start, int end, char[] buf, int off)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 drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)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 drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)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 measureText(int start, int end, Paint p)16127 public float measureText(int start, int end, Paint p) { 16128 return p.measureText(mChars, start + mStart, end - start); 16129 } 16130 getTextWidths(int start, int end, float[] widths, Paint p)16131 public int getTextWidths(int start, int end, float[] widths, Paint p) { 16132 return p.getTextWidths(mChars, start + mStart, end - start, widths); 16133 } 16134 getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)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 getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)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 Marquee(TextView v)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 tick()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 stop()16238 void stop() { 16239 mStatus = MARQUEE_STOPPED; 16240 mChoreographer.removeFrameCallback(mStartCallback); 16241 mChoreographer.removeFrameCallback(mRestartCallback); 16242 mChoreographer.removeFrameCallback(mTickCallback); 16243 resetScroll(); 16244 } 16245 resetScroll()16246 private void resetScroll() { 16247 mScroll = 0.0f; 16248 final TextView textView = mView.get(); 16249 if (textView != null) textView.invalidate(); 16250 } 16251 start(int repeatLimit)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 getGhostOffset()16277 float getGhostOffset() { 16278 return mGhostOffset; 16279 } 16280 getScroll()16281 float getScroll() { 16282 return mScroll; 16283 } 16284 getMaxFadeScroll()16285 float getMaxFadeScroll() { 16286 return mMaxFadeScroll; 16287 } 16288 shouldDrawLeftFade()16289 boolean shouldDrawLeftFade() { 16290 return mScroll <= mFadeStop; 16291 } 16292 shouldDrawGhost()16293 boolean shouldDrawGhost() { 16294 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 16295 } 16296 isRunning()16297 boolean isRunning() { 16298 return mStatus == MARQUEE_RUNNING; 16299 } 16300 isStopped()16301 boolean isStopped() { 16302 return mStatus == MARQUEE_STOPPED; 16303 } 16304 } 16305 16306 private class ChangeWatcher implements TextWatcher, SpanWatcher { 16307 16308 private CharSequence mBeforeText; 16309 beforeTextChanged(CharSequence buffer, int start, int before, int after)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 onTextChanged(CharSequence buffer, int start, int before, int after)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 afterTextChanged(Editable buffer)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 onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)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 onSpanAdded(Spannable buf, Object what, int s, int e)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 onSpanRemoved(Spannable buf, Object what, int s, int e)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 onInputConnectionOpenedInternal(@onNull InputConnection ic, @NonNull EditorInfo editorInfo, @Nullable Handler handler)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 onInputConnectionClosedInternal()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 onReceiveContent(@onNull ContentInfo payload)16408 public ContentInfo onReceiveContent(@NonNull ContentInfo payload) { 16409 if (mEditor != null) { 16410 return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload); 16411 } 16412 return payload; 16413 } 16414 logCursor(String location, @Nullable String msgFormat, Object ... msgArgs)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 onCreateViewTranslationRequest(@onNull int[] supportedFormats, @NonNull Consumer<ViewTranslationRequest> requestsCollector)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