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.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY; 30 import static android.view.accessibility.Flags.FLAG_A11Y_CHARACTER_IN_WINDOW_API; 31 import static android.view.accessibility.Flags.a11yCharacterInWindowApi; 32 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; 33 import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY; 34 import static android.view.inputmethod.Flags.initiationWithoutInputConnection; 35 36 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE; 37 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH; 38 39 import android.R; 40 import android.annotation.CallSuper; 41 import android.annotation.CheckResult; 42 import android.annotation.ColorInt; 43 import android.annotation.DrawableRes; 44 import android.annotation.FlaggedApi; 45 import android.annotation.FloatRange; 46 import android.annotation.IntDef; 47 import android.annotation.IntRange; 48 import android.annotation.NonNull; 49 import android.annotation.Nullable; 50 import android.annotation.Px; 51 import android.annotation.RequiresPermission; 52 import android.annotation.Size; 53 import android.annotation.StringRes; 54 import android.annotation.StyleRes; 55 import android.annotation.TestApi; 56 import android.annotation.XmlRes; 57 import android.app.Activity; 58 import android.app.PendingIntent; 59 import android.app.assist.AssistStructure; 60 import android.app.compat.CompatChanges; 61 import android.compat.annotation.ChangeId; 62 import android.compat.annotation.EnabledSince; 63 import android.compat.annotation.UnsupportedAppUsage; 64 import android.content.ClipData; 65 import android.content.ClipDescription; 66 import android.content.ClipboardManager; 67 import android.content.Context; 68 import android.content.Intent; 69 import android.content.UndoManager; 70 import android.content.pm.PackageManager; 71 import android.content.res.ColorStateList; 72 import android.content.res.CompatibilityInfo; 73 import android.content.res.Configuration; 74 import android.content.res.FontScaleConverterFactory; 75 import android.content.res.Resources; 76 import android.content.res.TypedArray; 77 import android.content.res.XmlResourceParser; 78 import android.graphics.BaseCanvas; 79 import android.graphics.BlendMode; 80 import android.graphics.Canvas; 81 import android.graphics.Color; 82 import android.graphics.Insets; 83 import android.graphics.Matrix; 84 import android.graphics.Paint; 85 import android.graphics.Paint.FontMetricsInt; 86 import android.graphics.Path; 87 import android.graphics.PointF; 88 import android.graphics.PorterDuff; 89 import android.graphics.Rect; 90 import android.graphics.RectF; 91 import android.graphics.Typeface; 92 import android.graphics.drawable.Drawable; 93 import android.graphics.fonts.FontStyle; 94 import android.graphics.fonts.FontVariationAxis; 95 import android.graphics.text.LineBreakConfig; 96 import android.icu.text.DecimalFormatSymbols; 97 import android.os.AsyncTask; 98 import android.os.Build; 99 import android.os.Build.VERSION_CODES; 100 import android.os.Bundle; 101 import android.os.CancellationSignal; 102 import android.os.Handler; 103 import android.os.LocaleList; 104 import android.os.Parcel; 105 import android.os.Parcelable; 106 import android.os.ParcelableParcel; 107 import android.os.Process; 108 import android.os.SystemClock; 109 import android.os.UserHandle; 110 import android.provider.Settings; 111 import android.text.BoringLayout; 112 import android.text.DynamicLayout; 113 import android.text.Editable; 114 import android.text.GetChars; 115 import android.text.GraphemeClusterSegmentFinder; 116 import android.text.GraphicsOperations; 117 import android.text.Highlights; 118 import android.text.InputFilter; 119 import android.text.InputType; 120 import android.text.Layout; 121 import android.text.NoCopySpan; 122 import android.text.ParcelableSpan; 123 import android.text.PrecomputedText; 124 import android.text.SegmentFinder; 125 import android.text.Selection; 126 import android.text.SpanWatcher; 127 import android.text.Spannable; 128 import android.text.SpannableStringBuilder; 129 import android.text.Spanned; 130 import android.text.SpannedString; 131 import android.text.StaticLayout; 132 import android.text.TextDirectionHeuristic; 133 import android.text.TextDirectionHeuristics; 134 import android.text.TextPaint; 135 import android.text.TextUtils; 136 import android.text.TextUtils.TruncateAt; 137 import android.text.TextWatcher; 138 import android.text.WordSegmentFinder; 139 import android.text.method.AllCapsTransformationMethod; 140 import android.text.method.ArrowKeyMovementMethod; 141 import android.text.method.DateKeyListener; 142 import android.text.method.DateTimeKeyListener; 143 import android.text.method.DialerKeyListener; 144 import android.text.method.DigitsKeyListener; 145 import android.text.method.KeyListener; 146 import android.text.method.LinkMovementMethod; 147 import android.text.method.MetaKeyKeyListener; 148 import android.text.method.MovementMethod; 149 import android.text.method.OffsetMapping; 150 import android.text.method.PasswordTransformationMethod; 151 import android.text.method.SingleLineTransformationMethod; 152 import android.text.method.TextKeyListener; 153 import android.text.method.TimeKeyListener; 154 import android.text.method.TransformationMethod; 155 import android.text.method.TransformationMethod2; 156 import android.text.method.WordIterator; 157 import android.text.style.CharacterStyle; 158 import android.text.style.ClickableSpan; 159 import android.text.style.ParagraphStyle; 160 import android.text.style.SpellCheckSpan; 161 import android.text.style.SuggestionSpan; 162 import android.text.style.URLSpan; 163 import android.text.style.UpdateAppearance; 164 import android.text.util.Linkify; 165 import android.util.ArraySet; 166 import android.util.AttributeSet; 167 import android.util.DisplayMetrics; 168 import android.util.IntArray; 169 import android.util.Log; 170 import android.util.SparseIntArray; 171 import android.util.TypedValue; 172 import android.view.AccessibilityIterators.TextSegmentIterator; 173 import android.view.ActionMode; 174 import android.view.Choreographer; 175 import android.view.ContentInfo; 176 import android.view.ContextMenu; 177 import android.view.DragEvent; 178 import android.view.Gravity; 179 import android.view.HapticFeedbackConstants; 180 import android.view.InputDevice; 181 import android.view.KeyCharacterMap; 182 import android.view.KeyEvent; 183 import android.view.MotionEvent; 184 import android.view.PointerIcon; 185 import android.view.View; 186 import android.view.ViewConfiguration; 187 import android.view.ViewDebug; 188 import android.view.ViewGroup; 189 import android.view.ViewGroup.LayoutParams; 190 import android.view.ViewHierarchyEncoder; 191 import android.view.ViewParent; 192 import android.view.ViewRootImpl; 193 import android.view.ViewStructure; 194 import android.view.ViewTreeObserver; 195 import android.view.accessibility.AccessibilityEvent; 196 import android.view.accessibility.AccessibilityManager; 197 import android.view.accessibility.AccessibilityNodeInfo; 198 import android.view.animation.AnimationUtils; 199 import android.view.autofill.AutofillManager; 200 import android.view.autofill.AutofillValue; 201 import android.view.contentcapture.ContentCaptureManager; 202 import android.view.contentcapture.ContentCaptureSession; 203 import android.view.inputmethod.BaseInputConnection; 204 import android.view.inputmethod.CompletionInfo; 205 import android.view.inputmethod.CorrectionInfo; 206 import android.view.inputmethod.CursorAnchorInfo; 207 import android.view.inputmethod.DeleteGesture; 208 import android.view.inputmethod.DeleteRangeGesture; 209 import android.view.inputmethod.EditorBoundsInfo; 210 import android.view.inputmethod.EditorInfo; 211 import android.view.inputmethod.ExtractedText; 212 import android.view.inputmethod.ExtractedTextRequest; 213 import android.view.inputmethod.HandwritingGesture; 214 import android.view.inputmethod.InputConnection; 215 import android.view.inputmethod.InputMethodManager; 216 import android.view.inputmethod.InsertGesture; 217 import android.view.inputmethod.InsertModeGesture; 218 import android.view.inputmethod.JoinOrSplitGesture; 219 import android.view.inputmethod.PreviewableHandwritingGesture; 220 import android.view.inputmethod.RemoveSpaceGesture; 221 import android.view.inputmethod.SelectGesture; 222 import android.view.inputmethod.SelectRangeGesture; 223 import android.view.inputmethod.TextAppearanceInfo; 224 import android.view.inputmethod.TextBoundsInfo; 225 import android.view.inspector.InspectableProperty; 226 import android.view.inspector.InspectableProperty.EnumEntry; 227 import android.view.inspector.InspectableProperty.FlagEntry; 228 import android.view.textclassifier.TextClassification; 229 import android.view.textclassifier.TextClassificationContext; 230 import android.view.textclassifier.TextClassificationManager; 231 import android.view.textclassifier.TextClassifier; 232 import android.view.textclassifier.TextLinks; 233 import android.view.textservice.SpellCheckerSubtype; 234 import android.view.textservice.TextServicesManager; 235 import android.view.translation.TranslationRequestValue; 236 import android.view.translation.TranslationSpec; 237 import android.view.translation.UiTranslationController; 238 import android.view.translation.ViewTranslationCallback; 239 import android.view.translation.ViewTranslationRequest; 240 import android.widget.RemoteViews.RemoteView; 241 242 import com.android.internal.accessibility.util.AccessibilityUtils; 243 import com.android.internal.annotations.VisibleForTesting; 244 import com.android.internal.graphics.ColorUtils; 245 import com.android.internal.inputmethod.EditableInputConnection; 246 import com.android.internal.logging.MetricsLogger; 247 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 248 import com.android.internal.util.ArrayUtils; 249 import com.android.internal.util.FastMath; 250 import com.android.internal.util.Preconditions; 251 import com.android.text.flags.Flags; 252 253 import libcore.util.EmptyArray; 254 255 import org.xmlpull.v1.XmlPullParserException; 256 257 import java.io.IOException; 258 import java.lang.annotation.Retention; 259 import java.lang.annotation.RetentionPolicy; 260 import java.lang.ref.WeakReference; 261 import java.util.ArrayList; 262 import java.util.Arrays; 263 import java.util.List; 264 import java.util.Locale; 265 import java.util.Objects; 266 import java.util.Set; 267 import java.util.concurrent.CompletableFuture; 268 import java.util.concurrent.TimeUnit; 269 import java.util.function.Consumer; 270 import java.util.function.Supplier; 271 import java.util.regex.Matcher; 272 import java.util.regex.Pattern; 273 274 /** 275 * A user interface element that displays text to the user. 276 * To provide user-editable text, see {@link EditText}. 277 * <p> 278 * The following code sample shows a typical use, with an XML layout 279 * and code to modify the contents of the text view: 280 * </p> 281 282 * <pre> 283 * <LinearLayout 284 xmlns:android="http://schemas.android.com/apk/res/android" 285 android:layout_width="match_parent" 286 android:layout_height="match_parent"> 287 * <TextView 288 * android:id="@+id/text_view_id" 289 * android:layout_height="wrap_content" 290 * android:layout_width="wrap_content" 291 * android:text="@string/hello" /> 292 * </LinearLayout> 293 * </pre> 294 * <p> 295 * This code sample demonstrates how to modify the contents of the text view 296 * defined in the previous XML layout: 297 * </p> 298 * <pre> 299 * public class MainActivity extends Activity { 300 * 301 * protected void onCreate(Bundle savedInstanceState) { 302 * super.onCreate(savedInstanceState); 303 * setContentView(R.layout.activity_main); 304 * final TextView helloTextView = (TextView) findViewById(R.id.text_view_id); 305 * helloTextView.setText(R.string.user_greeting); 306 * } 307 * } 308 * </pre> 309 * <p> 310 * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>. 311 * </p> 312 * <p> 313 * <b>XML attributes</b> 314 * <p> 315 * See {@link android.R.styleable#TextView TextView Attributes}, 316 * {@link android.R.styleable#View View Attributes} 317 * 318 * @attr ref android.R.styleable#TextView_text 319 * @attr ref android.R.styleable#TextView_bufferType 320 * @attr ref android.R.styleable#TextView_hint 321 * @attr ref android.R.styleable#TextView_textColor 322 * @attr ref android.R.styleable#TextView_textColorHighlight 323 * @attr ref android.R.styleable#TextView_textColorHint 324 * @attr ref android.R.styleable#TextView_textAppearance 325 * @attr ref android.R.styleable#TextView_textColorLink 326 * @attr ref android.R.styleable#TextView_textFontWeight 327 * @attr ref android.R.styleable#TextView_textSize 328 * @attr ref android.R.styleable#TextView_textScaleX 329 * @attr ref android.R.styleable#TextView_fontFamily 330 * @attr ref android.R.styleable#TextView_typeface 331 * @attr ref android.R.styleable#TextView_textStyle 332 * @attr ref android.R.styleable#TextView_cursorVisible 333 * @attr ref android.R.styleable#TextView_maxLines 334 * @attr ref android.R.styleable#TextView_maxHeight 335 * @attr ref android.R.styleable#TextView_lines 336 * @attr ref android.R.styleable#TextView_height 337 * @attr ref android.R.styleable#TextView_minLines 338 * @attr ref android.R.styleable#TextView_minHeight 339 * @attr ref android.R.styleable#TextView_maxEms 340 * @attr ref android.R.styleable#TextView_maxWidth 341 * @attr ref android.R.styleable#TextView_ems 342 * @attr ref android.R.styleable#TextView_width 343 * @attr ref android.R.styleable#TextView_minEms 344 * @attr ref android.R.styleable#TextView_minWidth 345 * @attr ref android.R.styleable#TextView_gravity 346 * @attr ref android.R.styleable#TextView_scrollHorizontally 347 * @attr ref android.R.styleable#TextView_password 348 * @attr ref android.R.styleable#TextView_singleLine 349 * @attr ref android.R.styleable#TextView_selectAllOnFocus 350 * @attr ref android.R.styleable#TextView_includeFontPadding 351 * @attr ref android.R.styleable#TextView_maxLength 352 * @attr ref android.R.styleable#TextView_shadowColor 353 * @attr ref android.R.styleable#TextView_shadowDx 354 * @attr ref android.R.styleable#TextView_shadowDy 355 * @attr ref android.R.styleable#TextView_shadowRadius 356 * @attr ref android.R.styleable#TextView_autoLink 357 * @attr ref android.R.styleable#TextView_linksClickable 358 * @attr ref android.R.styleable#TextView_numeric 359 * @attr ref android.R.styleable#TextView_digits 360 * @attr ref android.R.styleable#TextView_phoneNumber 361 * @attr ref android.R.styleable#TextView_inputMethod 362 * @attr ref android.R.styleable#TextView_capitalize 363 * @attr ref android.R.styleable#TextView_autoText 364 * @attr ref android.R.styleable#TextView_editable 365 * @attr ref android.R.styleable#TextView_freezesText 366 * @attr ref android.R.styleable#TextView_ellipsize 367 * @attr ref android.R.styleable#TextView_drawableTop 368 * @attr ref android.R.styleable#TextView_drawableBottom 369 * @attr ref android.R.styleable#TextView_drawableRight 370 * @attr ref android.R.styleable#TextView_drawableLeft 371 * @attr ref android.R.styleable#TextView_drawableStart 372 * @attr ref android.R.styleable#TextView_drawableEnd 373 * @attr ref android.R.styleable#TextView_drawablePadding 374 * @attr ref android.R.styleable#TextView_drawableTint 375 * @attr ref android.R.styleable#TextView_drawableTintMode 376 * @attr ref android.R.styleable#TextView_lineSpacingExtra 377 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 378 * @attr ref android.R.styleable#TextView_justificationMode 379 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 380 * @attr ref android.R.styleable#TextView_inputType 381 * @attr ref android.R.styleable#TextView_imeOptions 382 * @attr ref android.R.styleable#TextView_privateImeOptions 383 * @attr ref android.R.styleable#TextView_imeActionLabel 384 * @attr ref android.R.styleable#TextView_imeActionId 385 * @attr ref android.R.styleable#TextView_editorExtras 386 * @attr ref android.R.styleable#TextView_elegantTextHeight 387 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 388 * @attr ref android.R.styleable#TextView_letterSpacing 389 * @attr ref android.R.styleable#TextView_fontFeatureSettings 390 * @attr ref android.R.styleable#TextView_fontVariationSettings 391 * @attr ref android.R.styleable#TextView_breakStrategy 392 * @attr ref android.R.styleable#TextView_hyphenationFrequency 393 * @attr ref android.R.styleable#TextView_lineBreakStyle 394 * @attr ref android.R.styleable#TextView_lineBreakWordStyle 395 * @attr ref android.R.styleable#TextView_autoSizeTextType 396 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 397 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 398 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 399 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 400 * @attr ref android.R.styleable#TextView_textCursorDrawable 401 * @attr ref android.R.styleable#TextView_textSelectHandle 402 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 403 * @attr ref android.R.styleable#TextView_textSelectHandleRight 404 * @attr ref android.R.styleable#TextView_allowUndo 405 * @attr ref android.R.styleable#TextView_enabled 406 */ 407 @RemoteView 408 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 409 static final String LOG_TAG = "TextView"; 410 static final boolean DEBUG_EXTRACT = false; 411 static final boolean DEBUG_CURSOR = false; 412 413 private static final float[] TEMP_POSITION = new float[2]; 414 415 // Enum for the "typeface" XML parameter. 416 // TODO: How can we get this from the XML instead of hardcoding it here? 417 /** @hide */ 418 @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE}) 419 @Retention(RetentionPolicy.SOURCE) 420 public @interface XMLTypefaceAttr{} 421 private static final int DEFAULT_TYPEFACE = -1; 422 private static final int SANS = 1; 423 private static final int SERIF = 2; 424 private static final int MONOSPACE = 3; 425 426 // Enum for the "ellipsize" XML parameter. 427 private static final int ELLIPSIZE_NOT_SET = -1; 428 private static final int ELLIPSIZE_NONE = 0; 429 private static final int ELLIPSIZE_START = 1; 430 private static final int ELLIPSIZE_MIDDLE = 2; 431 private static final int ELLIPSIZE_END = 3; 432 private static final int ELLIPSIZE_MARQUEE = 4; 433 434 // Bitfield for the "numeric" XML parameter. 435 // TODO: How can we get this from the XML instead of hardcoding it here? 436 private static final int SIGNED = 2; 437 private static final int DECIMAL = 4; 438 439 /** 440 * Draw marquee text with fading edges as usual 441 */ 442 private static final int MARQUEE_FADE_NORMAL = 0; 443 444 /** 445 * Draw marquee text as ellipsize end while inactive instead of with the fade. 446 * (Useful for devices where the fade can be expensive if overdone) 447 */ 448 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1; 449 450 /** 451 * Draw marquee text with fading edges because it is currently active/animating. 452 */ 453 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2; 454 455 @UnsupportedAppUsage 456 private static final int LINES = 1; 457 private static final int EMS = LINES; 458 private static final int PIXELS = 2; 459 460 // Maximum text length for single line input. 461 private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000; 462 private InputFilter.LengthFilter mSingleLineLengthFilter = null; 463 464 private static final RectF TEMP_RECTF = new RectF(); 465 466 /** @hide */ 467 static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger 468 private static final int ANIMATED_SCROLL_GAP = 250; 469 470 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 471 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 472 473 private static final int CHANGE_WATCHER_PRIORITY = 100; 474 475 /** 476 * The span priority of the {@link OffsetMapping} that is set on the text. It must be 477 * higher than the {@link DynamicLayout}'s {@link TextWatcher}, so that the transformed text is 478 * updated before {@link DynamicLayout#reflow(CharSequence, int, int, int)} being triggered 479 * by {@link TextWatcher#onTextChanged(CharSequence, int, int, int)}. 480 */ 481 private static final int OFFSET_MAPPING_SPAN_PRIORITY = 200; 482 483 // New state used to change background based on whether this TextView is multiline. 484 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline }; 485 486 // Accessibility action to share selected text. 487 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000; 488 489 /** 490 * @hide 491 */ 492 // Accessibility action start id for "process text" actions. 493 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100; 494 495 /** Accessibility action start id for "smart" actions. @hide */ 496 static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000; 497 498 // Stable extra data keys supported by TextView. 499 private static final List<String> ACCESSIBILITY_EXTRA_DATA_KEYS = List.of( 500 EXTRA_DATA_RENDERING_INFO_KEY, 501 EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY 502 ); 503 504 // Flagged and stable extra data keys supported by TextView. 505 @FlaggedApi(FLAG_A11Y_CHARACTER_IN_WINDOW_API) 506 private static final List<String> ACCESSIBILITY_EXTRA_DATA_KEYS_FLAGGED = List.of( 507 EXTRA_DATA_RENDERING_INFO_KEY, 508 EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, 509 EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY 510 ); 511 512 /** 513 * @hide 514 */ 515 @TestApi 516 public static final int PROCESS_TEXT_REQUEST_CODE = 100; 517 518 /** 519 * Return code of {@link #doKeyDown}. 520 */ 521 private static final int KEY_EVENT_NOT_HANDLED = 0; 522 private static final int KEY_EVENT_HANDLED = -1; 523 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1; 524 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2; 525 526 private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; 527 528 // The default value of the line break style. 529 private static final int DEFAULT_LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_NONE; 530 531 // The default value of the line break word style. 532 private static final int DEFAULT_LINE_BREAK_WORD_STYLE = 533 LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; 534 535 /** 536 * This change ID enables the fallback text line spacing (line height) for BoringLayout. 537 * @hide 538 */ 539 @ChangeId 540 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) 541 public static final long BORINGLAYOUT_FALLBACK_LINESPACING = 210923482L; // buganizer id 542 543 /** 544 * This change ID enables the fallback text line spacing (line height) for StaticLayout. 545 * @hide 546 */ 547 @ChangeId 548 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.P) 549 public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id 550 551 552 /** 553 * This change ID enables the bounding box based layout. 554 * @hide 555 */ 556 @ChangeId 557 @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM) 558 public static final long USE_BOUNDS_FOR_WIDTH = 63938206; // buganizer id 559 560 // System wide time for last cut, copy or text changed action. 561 static long sLastCutCopyOrTextChangedTime; 562 private ColorStateList mTextColor; 563 private ColorStateList mHintTextColor; 564 private ColorStateList mLinkTextColor; 565 @ViewDebug.ExportedProperty(category = "text") 566 567 /** 568 * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead. 569 */ 570 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 571 private int mCurTextColor; 572 573 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 574 private int mCurHintTextColor; 575 private boolean mFreezesText; 576 577 @UnsupportedAppUsage 578 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 579 @UnsupportedAppUsage 580 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 581 582 @UnsupportedAppUsage 583 private float mShadowRadius; 584 @UnsupportedAppUsage 585 private float mShadowDx; 586 @UnsupportedAppUsage 587 private float mShadowDy; 588 private int mShadowColor; 589 590 private int mLastOrientation; 591 592 private boolean mPreDrawRegistered; 593 private boolean mPreDrawListenerDetached; 594 595 private TextClassifier mTextClassifier; 596 private TextClassifier mTextClassificationSession; 597 private TextClassificationContext mTextClassificationContext; 598 599 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is 600 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse 601 // the view hierarchy. On the other hand, if the user is using the movement key to traverse 602 // views (i.e. the first movement was to traverse out of this view, or this view was traversed 603 // into by the user holding the movement key down) then we shouldn't prevent the focus from 604 // changing. 605 private boolean mPreventDefaultMovement; 606 607 private TextUtils.TruncateAt mEllipsize; 608 609 // A flag to indicate the cursor was hidden by IME. 610 private boolean mImeIsConsumingInput; 611 612 // Whether cursor is visible without regard to {@link mImeConsumesInput}. 613 // {@code true} is the default value. 614 private boolean mCursorVisibleFromAttr = true; 615 616 static class Drawables { 617 static final int LEFT = 0; 618 static final int TOP = 1; 619 static final int RIGHT = 2; 620 static final int BOTTOM = 3; 621 622 static final int DRAWABLE_NONE = -1; 623 static final int DRAWABLE_RIGHT = 0; 624 static final int DRAWABLE_LEFT = 1; 625 626 final Rect mCompoundRect = new Rect(); 627 628 final Drawable[] mShowing = new Drawable[4]; 629 630 ColorStateList mTintList; 631 BlendMode mBlendMode; 632 boolean mHasTint; 633 boolean mHasTintMode; 634 635 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp; 636 Drawable mDrawableLeftInitial, mDrawableRightInitial; 637 638 boolean mIsRtlCompatibilityMode; 639 boolean mOverride; 640 641 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight, 642 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp; 643 644 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight, 645 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp; 646 647 int mDrawablePadding; 648 649 int mDrawableSaved = DRAWABLE_NONE; 650 Drawables(Context context)651 public Drawables(Context context) { 652 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 653 mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1 654 || !context.getApplicationInfo().hasRtlSupport(); 655 mOverride = false; 656 } 657 658 /** 659 * @return {@code true} if this object contains metadata that needs to 660 * be retained, {@code false} otherwise 661 */ 662 public boolean hasMetadata() { 663 return mDrawablePadding != 0 || mHasTintMode || mHasTint; 664 } 665 666 /** 667 * Updates the list of displayed drawables to account for the current 668 * layout direction. 669 * 670 * @param layoutDirection the current layout direction 671 * @return {@code true} if the displayed drawables changed 672 */ 673 public boolean resolveWithLayoutDirection(int layoutDirection) { 674 final Drawable previousLeft = mShowing[Drawables.LEFT]; 675 final Drawable previousRight = mShowing[Drawables.RIGHT]; 676 677 // First reset "left" and "right" drawables to their initial values 678 mShowing[Drawables.LEFT] = mDrawableLeftInitial; 679 mShowing[Drawables.RIGHT] = mDrawableRightInitial; 680 681 if (mIsRtlCompatibilityMode) { 682 // Use "start" drawable as "left" drawable if the "left" drawable was not defined 683 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) { 684 mShowing[Drawables.LEFT] = mDrawableStart; 685 mDrawableSizeLeft = mDrawableSizeStart; 686 mDrawableHeightLeft = mDrawableHeightStart; 687 } 688 // Use "end" drawable as "right" drawable if the "right" drawable was not defined 689 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) { 690 mShowing[Drawables.RIGHT] = mDrawableEnd; 691 mDrawableSizeRight = mDrawableSizeEnd; 692 mDrawableHeightRight = mDrawableHeightEnd; 693 } 694 } else { 695 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right" 696 // drawable if and only if they have been defined 697 switch(layoutDirection) { 698 case LAYOUT_DIRECTION_RTL: 699 if (mOverride) { 700 mShowing[Drawables.RIGHT] = mDrawableStart; 701 mDrawableSizeRight = mDrawableSizeStart; 702 mDrawableHeightRight = mDrawableHeightStart; 703 704 mShowing[Drawables.LEFT] = mDrawableEnd; 705 mDrawableSizeLeft = mDrawableSizeEnd; 706 mDrawableHeightLeft = mDrawableHeightEnd; 707 } 708 break; 709 710 case LAYOUT_DIRECTION_LTR: 711 default: 712 if (mOverride) { 713 mShowing[Drawables.LEFT] = mDrawableStart; 714 mDrawableSizeLeft = mDrawableSizeStart; 715 mDrawableHeightLeft = mDrawableHeightStart; 716 717 mShowing[Drawables.RIGHT] = mDrawableEnd; 718 mDrawableSizeRight = mDrawableSizeEnd; 719 mDrawableHeightRight = mDrawableHeightEnd; 720 } 721 break; 722 } 723 } 724 725 applyErrorDrawableIfNeeded(layoutDirection); 726 727 return mShowing[Drawables.LEFT] != previousLeft 728 || mShowing[Drawables.RIGHT] != previousRight; 729 } 730 731 public void setErrorDrawable(Drawable dr, TextView tv) { 732 if (mDrawableError != dr && mDrawableError != null) { 733 mDrawableError.setCallback(null); 734 } 735 mDrawableError = dr; 736 737 if (mDrawableError != null) { 738 final Rect compoundRect = mCompoundRect; 739 final int[] state = tv.getDrawableState(); 740 741 mDrawableError.setState(state); 742 mDrawableError.copyBounds(compoundRect); 743 mDrawableError.setCallback(tv); 744 mDrawableSizeError = compoundRect.width(); 745 mDrawableHeightError = compoundRect.height(); 746 } else { 747 mDrawableSizeError = mDrawableHeightError = 0; 748 } 749 } 750 751 private void applyErrorDrawableIfNeeded(int layoutDirection) { 752 // first restore the initial state if needed 753 switch (mDrawableSaved) { 754 case DRAWABLE_LEFT: 755 mShowing[Drawables.LEFT] = mDrawableTemp; 756 mDrawableSizeLeft = mDrawableSizeTemp; 757 mDrawableHeightLeft = mDrawableHeightTemp; 758 break; 759 case DRAWABLE_RIGHT: 760 mShowing[Drawables.RIGHT] = mDrawableTemp; 761 mDrawableSizeRight = mDrawableSizeTemp; 762 mDrawableHeightRight = mDrawableHeightTemp; 763 break; 764 case DRAWABLE_NONE: 765 default: 766 } 767 // then, if needed, assign the Error drawable to the correct location 768 if (mDrawableError != null) { 769 switch(layoutDirection) { 770 case LAYOUT_DIRECTION_RTL: 771 mDrawableSaved = DRAWABLE_LEFT; 772 773 mDrawableTemp = mShowing[Drawables.LEFT]; 774 mDrawableSizeTemp = mDrawableSizeLeft; 775 mDrawableHeightTemp = mDrawableHeightLeft; 776 777 mShowing[Drawables.LEFT] = mDrawableError; 778 mDrawableSizeLeft = mDrawableSizeError; 779 mDrawableHeightLeft = mDrawableHeightError; 780 break; 781 case LAYOUT_DIRECTION_LTR: 782 default: 783 mDrawableSaved = DRAWABLE_RIGHT; 784 785 mDrawableTemp = mShowing[Drawables.RIGHT]; 786 mDrawableSizeTemp = mDrawableSizeRight; 787 mDrawableHeightTemp = mDrawableHeightRight; 788 789 mShowing[Drawables.RIGHT] = mDrawableError; 790 mDrawableSizeRight = mDrawableSizeError; 791 mDrawableHeightRight = mDrawableHeightError; 792 break; 793 } 794 } 795 } 796 } 797 798 @UnsupportedAppUsage 799 Drawables mDrawables; 800 801 @UnsupportedAppUsage 802 private CharWrapper mCharWrapper; 803 804 @UnsupportedAppUsage(trackingBug = 124050217) 805 private Marquee mMarquee; 806 @UnsupportedAppUsage 807 private boolean mRestartMarquee; 808 809 private int mMarqueeRepeatLimit = 3; 810 811 private int mLastLayoutDirection = -1; 812 813 /** 814 * On some devices the fading edges add a performance penalty if used 815 * extensively in the same layout. This mode indicates how the marquee 816 * is currently being shown, if applicable. (mEllipsize will == MARQUEE) 817 */ 818 @UnsupportedAppUsage 819 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 820 821 /** 822 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores 823 * the layout that should be used when the mode switches. 824 */ 825 @UnsupportedAppUsage 826 private Layout mSavedMarqueeModeLayout; 827 828 // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal() 829 @ViewDebug.ExportedProperty(category = "text") 830 @UnsupportedAppUsage 831 private @Nullable CharSequence mText; 832 private @Nullable Spannable mSpannable; 833 private @Nullable PrecomputedText mPrecomputed; 834 835 @UnsupportedAppUsage 836 private CharSequence mTransformed; 837 @UnsupportedAppUsage 838 private BufferType mBufferType = BufferType.NORMAL; 839 840 private CharSequence mHint; 841 @UnsupportedAppUsage 842 private Layout mHintLayout; 843 private boolean mHideHint; 844 845 private MovementMethod mMovement; 846 847 private TransformationMethod mTransformation; 848 @UnsupportedAppUsage 849 private boolean mAllowTransformationLengthChange; 850 @UnsupportedAppUsage 851 private ChangeWatcher mChangeWatcher; 852 853 @UnsupportedAppUsage(trackingBug = 123769451) 854 private ArrayList<TextWatcher> mListeners; 855 856 // display attributes 857 @UnsupportedAppUsage 858 private final TextPaint mTextPaint; 859 @UnsupportedAppUsage 860 private boolean mUserSetTextScaleX; 861 @UnsupportedAppUsage 862 private Layout mLayout; 863 private boolean mLocalesChanged = false; 864 private int mTextSizeUnit = -1; 865 private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; 866 private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; 867 868 // This is used to reflect the current user preference for changing font weight and making text 869 // more bold. 870 private int mFontWeightAdjustment; 871 private Typeface mOriginalTypeface; 872 873 // True if setKeyListener() has been explicitly called 874 private boolean mListenerChanged = false; 875 // True if internationalized input should be used for numbers and date and time. 876 private final boolean mUseInternationalizedInput; 877 878 // Fallback fonts that end up getting used should be allowed to affect line spacing. 879 private static final int FALLBACK_LINE_SPACING_NONE = 0; 880 private static final int FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY = 1; 881 private static final int FALLBACK_LINE_SPACING_ALL = 2; 882 883 private int mUseFallbackLineSpacing; 884 // True if the view text can be padded for compat reasons, when the view is translated. 885 private final boolean mUseTextPaddingForUiTranslation; 886 887 private boolean mUseBoundsForWidth; 888 private boolean mShiftDrawingOffsetForStartOverhang; 889 @Nullable private Paint.FontMetrics mMinimumFontMetrics; 890 @Nullable private Paint.FontMetrics mLocalePreferredFontMetrics; 891 private boolean mUseLocalePreferredLineHeightForMinimum; 892 893 @ViewDebug.ExportedProperty(category = "text") 894 @UnsupportedAppUsage 895 private int mGravity = Gravity.TOP | Gravity.START; 896 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 897 private boolean mHorizontallyScrolling; 898 899 private int mAutoLinkMask; 900 private boolean mLinksClickable = true; 901 902 @UnsupportedAppUsage 903 private float mSpacingMult = 1.0f; 904 @UnsupportedAppUsage 905 private float mSpacingAdd = 0.0f; 906 907 /** 908 * Remembers what line height was set to originally, before we broke it down into raw pixels. 909 * 910 * <p>This is stored as a complex dimension with both value and unit packed into one field! 911 * {@see TypedValue} 912 */ 913 private int mLineHeightComplexDimen; 914 915 private int mBreakStrategy; 916 private int mHyphenationFrequency; 917 private int mJustificationMode; 918 919 @UnsupportedAppUsage 920 private int mMaximum = Integer.MAX_VALUE; 921 @UnsupportedAppUsage 922 private int mMaxMode = LINES; 923 @UnsupportedAppUsage 924 private int mMinimum = 0; 925 @UnsupportedAppUsage 926 private int mMinMode = LINES; 927 928 @UnsupportedAppUsage 929 private int mOldMaximum = mMaximum; 930 @UnsupportedAppUsage 931 private int mOldMaxMode = mMaxMode; 932 933 @UnsupportedAppUsage 934 private int mMaxWidth = Integer.MAX_VALUE; 935 @UnsupportedAppUsage 936 private int mMaxWidthMode = PIXELS; 937 @UnsupportedAppUsage 938 private int mMinWidth = 0; 939 @UnsupportedAppUsage 940 private int mMinWidthMode = PIXELS; 941 942 @UnsupportedAppUsage 943 private boolean mSingleLine; 944 @UnsupportedAppUsage 945 private int mDesiredHeightAtMeasure = -1; 946 @UnsupportedAppUsage 947 private boolean mIncludePad = true; 948 private int mDeferScroll = -1; 949 950 // tmp primitives, so we don't alloc them on each draw 951 private Rect mTempRect; 952 private long mLastScroll; 953 private Scroller mScroller; 954 private TextPaint mTempTextPaint; 955 956 private Object mTempCursor; 957 private Matrix mTempMatrix; 958 959 @UnsupportedAppUsage 960 private BoringLayout.Metrics mBoring; 961 @UnsupportedAppUsage 962 private BoringLayout.Metrics mHintBoring; 963 @UnsupportedAppUsage 964 private BoringLayout mSavedLayout; 965 @UnsupportedAppUsage 966 private BoringLayout mSavedHintLayout; 967 968 @UnsupportedAppUsage 969 private TextDirectionHeuristic mTextDir; 970 971 private InputFilter[] mFilters = NO_FILTERS; 972 973 /** 974 * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is 975 * the same as {@link Process#myUserHandle()}. 976 * 977 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 978 * other apps may need to set this so that the system can use right user's resources and 979 * services such as input methods and spell checkers.</p> 980 * 981 * @see #setTextOperationUser(UserHandle) 982 */ 983 @Nullable 984 private UserHandle mTextOperationUser; 985 986 private volatile Locale mCurrentSpellCheckerLocaleCache; 987 988 // It is possible to have a selection even when mEditor is null (programmatically set, like when 989 // a link is pressed). These highlight-related fields do not go in mEditor. 990 @UnsupportedAppUsage 991 int mHighlightColor = 0x6633B5E5; 992 private Path mHighlightPath; 993 @UnsupportedAppUsage 994 private final Paint mHighlightPaint; 995 @UnsupportedAppUsage 996 private boolean mHighlightPathBogus = true; 997 998 private List<Path> mHighlightPaths; 999 private List<Paint> mHighlightPaints; 1000 private Highlights mHighlights; 1001 private int[] mSearchResultHighlights = null; 1002 private Paint mSearchResultHighlightPaint = null; 1003 private Paint mFocusedSearchResultHighlightPaint = null; 1004 private int mFocusedSearchResultHighlightColor = 0xFFFF9632; 1005 private int mSearchResultHighlightColor = 0xFFFFFF00; 1006 1007 private int mFocusedSearchResultIndex = -1; 1008 private int mGesturePreviewHighlightStart = -1; 1009 private int mGesturePreviewHighlightEnd = -1; 1010 private Paint mGesturePreviewHighlightPaint; 1011 private final List<Path> mPathRecyclePool = new ArrayList<>(); 1012 private boolean mHighlightPathsBogus = true; 1013 1014 // Although these fields are specific to editable text, they are not added to Editor because 1015 // they are defined by the TextView's style and are theme-dependent. 1016 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 1017 int mCursorDrawableRes; 1018 private Drawable mCursorDrawable; 1019 // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code 1020 // by removing it, but we would break apps targeting <= P that use it by reflection. 1021 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 1022 int mTextSelectHandleLeftRes; 1023 private Drawable mTextSelectHandleLeft; 1024 // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code 1025 // by removing it, but we would break apps targeting <= P that use it by reflection. 1026 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 1027 int mTextSelectHandleRightRes; 1028 private Drawable mTextSelectHandleRight; 1029 // Note: this might be stale if setTextSelectHandle is used. We could simplify the code 1030 // by removing it, but we would break apps targeting <= P that use it by reflection. 1031 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 1032 int mTextSelectHandleRes; 1033 private Drawable mTextSelectHandle; 1034 int mTextEditSuggestionItemLayout; 1035 int mTextEditSuggestionContainerLayout; 1036 int mTextEditSuggestionHighlightStyle; 1037 1038 private static final int NO_POINTER_ID = -1; 1039 /** 1040 * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among 1041 * TextView and the handle views which are rendered on popup windows. 1042 */ 1043 private int mPrimePointerId = NO_POINTER_ID; 1044 1045 /** 1046 * Whether the prime pointer is from the event delivered to selection handle or insertion 1047 * handle. 1048 */ 1049 private boolean mIsPrimePointerFromHandleView; 1050 1051 /** 1052 * {@link EditText} specific data, created on demand when one of the Editor fields is used. 1053 * See {@link #createEditorIfNeeded()}. 1054 */ 1055 @UnsupportedAppUsage 1056 private Editor mEditor; 1057 1058 private static final int DEVICE_PROVISIONED_UNKNOWN = 0; 1059 private static final int DEVICE_PROVISIONED_NO = 1; 1060 private static final int DEVICE_PROVISIONED_YES = 2; 1061 1062 /** 1063 * Some special options such as sharing selected text should only be shown if the device 1064 * is provisioned. Only check the provisioned state once for a given view instance. 1065 */ 1066 private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN; 1067 1068 /** 1069 * The last input source on this TextView. 1070 * 1071 * Use the SOURCE_TOUCHSCREEN as the default value for backward compatibility. There could be a 1072 * non UI event originated ActionMode initiation, e.g. API call, a11y events, etc. 1073 */ 1074 private int mLastInputSource = InputDevice.SOURCE_TOUCHSCREEN; 1075 1076 /** 1077 * The TextView does not auto-size text (default). 1078 */ 1079 public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; 1080 1081 /** 1082 * The TextView scales text size both horizontally and vertically to fit within the 1083 * container. 1084 */ 1085 public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; 1086 1087 /** @hide */ 1088 @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = { 1089 AUTO_SIZE_TEXT_TYPE_NONE, 1090 AUTO_SIZE_TEXT_TYPE_UNIFORM 1091 }) 1092 @Retention(RetentionPolicy.SOURCE) 1093 public @interface AutoSizeTextType {} 1094 // Default minimum size for auto-sizing text in scaled pixels. 1095 private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12; 1096 // Default maximum size for auto-sizing text in scaled pixels. 1097 private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112; 1098 // Default value for the step size in pixels. 1099 private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1; 1100 // Use this to specify that any of the auto-size configuration int values have not been set. 1101 private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f; 1102 // Auto-size text type. 1103 private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 1104 // Specify if auto-size text is needed. 1105 private boolean mNeedsAutoSizeText = false; 1106 // Step size for auto-sizing in pixels. 1107 private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1108 // Minimum text size for auto-sizing in pixels. 1109 private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1110 // Maximum text size for auto-sizing in pixels. 1111 private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1112 // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from 1113 // when auto-sizing text. 1114 private int[] mAutoSizeTextSizesInPx = EmptyArray.INT; 1115 // Specifies whether auto-size should use the provided auto size steps set or if it should 1116 // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and 1117 // mAutoSizeStepGranularityInPx. 1118 private boolean mHasPresetAutoSizeValues = false; 1119 1120 // Autofill-related attributes 1121 // 1122 // Indicates whether the text was set statically or dynamically, so it can be used to 1123 // sanitize autofill requests. 1124 private boolean mTextSetFromXmlOrResourceId = false; 1125 // Resource id used to set the text. 1126 private @StringRes int mTextId = Resources.ID_NULL; 1127 // Resource id used to set the hint. 1128 private @StringRes int mHintId = Resources.ID_NULL; 1129 // 1130 // End of autofill-related attributes 1131 1132 private Pattern mWhitespacePattern; 1133 1134 /** 1135 * Kick-start the font cache for the zygote process (to pay the cost of 1136 * initializing freetype for our default font only once). 1137 * @hide 1138 */ 1139 public static void preloadFontCache() { 1140 if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) { 1141 return; 1142 } 1143 Paint p = new Paint(); 1144 p.setAntiAlias(true); 1145 // Ensure that the Typeface is loaded here. 1146 // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto. 1147 // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here 1148 // since Paint.measureText can not be called without Typeface static initializer. 1149 p.setTypeface(Typeface.DEFAULT); 1150 // We don't care about the result, just the side-effect of measuring. 1151 p.measureText("H"); 1152 } 1153 1154 /** 1155 * Interface definition for a callback to be invoked when an action is 1156 * performed on the editor. 1157 */ 1158 public interface OnEditorActionListener { 1159 /** 1160 * Called when an action is being performed. 1161 * 1162 * @param v The view that was clicked. 1163 * @param actionId Identifier of the action. This will be either the 1164 * identifier you supplied, or {@link EditorInfo#IME_NULL 1165 * EditorInfo.IME_NULL} if being called due to the enter key 1166 * being pressed. Starting from Android 14, the action identifier will 1167 * also be included when triggered by an enter key if the input is 1168 * constrained to a single line. 1169 * @param event If triggered by an enter key, this is the event; 1170 * otherwise, this is null. 1171 * @return Return true if you have consumed the action, else false. 1172 */ 1173 boolean onEditorAction(TextView v, int actionId, KeyEvent event); 1174 } 1175 1176 public TextView(Context context) { 1177 this(context, null); 1178 } 1179 1180 public TextView(Context context, @Nullable AttributeSet attrs) { 1181 this(context, attrs, com.android.internal.R.attr.textViewStyle); 1182 } 1183 1184 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 1185 this(context, attrs, defStyleAttr, 0); 1186 } 1187 1188 @SuppressWarnings("deprecation") 1189 public TextView( 1190 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 1191 super(context, attrs, defStyleAttr, defStyleRes); 1192 1193 // TextView is important by default, unless app developer overrode attribute. 1194 if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { 1195 setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); 1196 } 1197 if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) { 1198 setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES); 1199 } 1200 1201 setTextInternal(""); 1202 1203 final Resources res = getResources(); 1204 final CompatibilityInfo compat = res.getCompatibilityInfo(); 1205 1206 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 1207 mTextPaint.density = res.getDisplayMetrics().density; 1208 mTextPaint.setCompatibilityScaling(compat.applicationScale); 1209 1210 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1211 mHighlightPaint.setCompatibilityScaling(compat.applicationScale); 1212 1213 mMovement = getDefaultMovementMethod(); 1214 1215 mTransformation = null; 1216 1217 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 1218 attributes.mTextColor = ColorStateList.valueOf(0xFF000000); 1219 attributes.mTextSize = 15; 1220 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; 1221 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; 1222 mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; 1223 mLastOrientation = getResources().getConfiguration().orientation; 1224 1225 final Resources.Theme theme = context.getTheme(); 1226 1227 /* 1228 * Look the appearance up without checking first if it exists because 1229 * almost every TextView has one and it greatly simplifies the logic 1230 * to be able to parse the appearance first and then let specific tags 1231 * for this View override it. 1232 */ 1233 TypedArray a = theme.obtainStyledAttributes(attrs, 1234 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); 1235 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance, 1236 attrs, a, defStyleAttr, defStyleRes); 1237 TypedArray appearance = null; 1238 int ap = a.getResourceId( 1239 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); 1240 a.recycle(); 1241 if (ap != -1) { 1242 appearance = theme.obtainStyledAttributes( 1243 ap, com.android.internal.R.styleable.TextAppearance); 1244 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance, 1245 null, appearance, 0, ap); 1246 } 1247 if (appearance != null) { 1248 readTextAppearance(context, appearance, attributes, false /* styleArray */); 1249 attributes.mFontFamilyExplicit = false; 1250 appearance.recycle(); 1251 } 1252 1253 boolean editable = getDefaultEditable(); 1254 CharSequence inputMethod = null; 1255 int numeric = 0; 1256 CharSequence digits = null; 1257 boolean phone = false; 1258 boolean autotext = false; 1259 int autocap = -1; 1260 int buffertype = 0; 1261 boolean selectallonfocus = false; 1262 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 1263 drawableBottom = null, drawableStart = null, drawableEnd = null; 1264 ColorStateList drawableTint = null; 1265 BlendMode drawableTintMode = null; 1266 int drawablePadding = 0; 1267 int ellipsize = ELLIPSIZE_NOT_SET; 1268 boolean singleLine = false; 1269 int maxlength = -1; 1270 CharSequence text = ""; 1271 CharSequence hint = null; 1272 boolean password = false; 1273 float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1274 float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1275 float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1276 int inputType = EditorInfo.TYPE_NULL; 1277 a = theme.obtainStyledAttributes( 1278 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); 1279 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a, 1280 defStyleAttr, defStyleRes); 1281 int firstBaselineToTopHeight = -1; 1282 int lastBaselineToBottomHeight = -1; 1283 float lineHeight = -1f; 1284 int lineHeightUnit = -1; 1285 boolean hasUseBoundForWidthValue = false; 1286 1287 readTextAppearance(context, a, attributes, true /* styleArray */); 1288 1289 int n = a.getIndexCount(); 1290 1291 // Must set id in a temporary variable because it will be reset by setText() 1292 boolean textIsSetFromXml = false; 1293 for (int i = 0; i < n; i++) { 1294 int attr = a.getIndex(i); 1295 1296 switch (attr) { 1297 case com.android.internal.R.styleable.TextView_editable: 1298 editable = a.getBoolean(attr, editable); 1299 break; 1300 1301 case com.android.internal.R.styleable.TextView_inputMethod: 1302 inputMethod = a.getText(attr); 1303 break; 1304 1305 case com.android.internal.R.styleable.TextView_numeric: 1306 numeric = a.getInt(attr, numeric); 1307 break; 1308 1309 case com.android.internal.R.styleable.TextView_digits: 1310 digits = a.getText(attr); 1311 break; 1312 1313 case com.android.internal.R.styleable.TextView_phoneNumber: 1314 phone = a.getBoolean(attr, phone); 1315 break; 1316 1317 case com.android.internal.R.styleable.TextView_autoText: 1318 autotext = a.getBoolean(attr, autotext); 1319 break; 1320 1321 case com.android.internal.R.styleable.TextView_capitalize: 1322 autocap = a.getInt(attr, autocap); 1323 break; 1324 1325 case com.android.internal.R.styleable.TextView_bufferType: 1326 buffertype = a.getInt(attr, buffertype); 1327 break; 1328 1329 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 1330 selectallonfocus = a.getBoolean(attr, selectallonfocus); 1331 break; 1332 1333 case com.android.internal.R.styleable.TextView_autoLink: 1334 mAutoLinkMask = a.getInt(attr, 0); 1335 break; 1336 1337 case com.android.internal.R.styleable.TextView_linksClickable: 1338 mLinksClickable = a.getBoolean(attr, true); 1339 break; 1340 1341 case com.android.internal.R.styleable.TextView_drawableLeft: 1342 drawableLeft = a.getDrawable(attr); 1343 break; 1344 1345 case com.android.internal.R.styleable.TextView_drawableTop: 1346 drawableTop = a.getDrawable(attr); 1347 break; 1348 1349 case com.android.internal.R.styleable.TextView_drawableRight: 1350 drawableRight = a.getDrawable(attr); 1351 break; 1352 1353 case com.android.internal.R.styleable.TextView_drawableBottom: 1354 drawableBottom = a.getDrawable(attr); 1355 break; 1356 1357 case com.android.internal.R.styleable.TextView_drawableStart: 1358 drawableStart = a.getDrawable(attr); 1359 break; 1360 1361 case com.android.internal.R.styleable.TextView_drawableEnd: 1362 drawableEnd = a.getDrawable(attr); 1363 break; 1364 1365 case com.android.internal.R.styleable.TextView_drawableTint: 1366 drawableTint = a.getColorStateList(attr); 1367 break; 1368 1369 case com.android.internal.R.styleable.TextView_drawableTintMode: 1370 drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1), 1371 drawableTintMode); 1372 break; 1373 1374 case com.android.internal.R.styleable.TextView_drawablePadding: 1375 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 1376 break; 1377 1378 case com.android.internal.R.styleable.TextView_maxLines: 1379 setMaxLines(a.getInt(attr, -1)); 1380 break; 1381 1382 case com.android.internal.R.styleable.TextView_maxHeight: 1383 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 1384 break; 1385 1386 case com.android.internal.R.styleable.TextView_lines: 1387 setLines(a.getInt(attr, -1)); 1388 break; 1389 1390 case com.android.internal.R.styleable.TextView_height: 1391 setHeight(a.getDimensionPixelSize(attr, -1)); 1392 break; 1393 1394 case com.android.internal.R.styleable.TextView_minLines: 1395 setMinLines(a.getInt(attr, -1)); 1396 break; 1397 1398 case com.android.internal.R.styleable.TextView_minHeight: 1399 setMinHeight(a.getDimensionPixelSize(attr, -1)); 1400 break; 1401 1402 case com.android.internal.R.styleable.TextView_maxEms: 1403 setMaxEms(a.getInt(attr, -1)); 1404 break; 1405 1406 case com.android.internal.R.styleable.TextView_maxWidth: 1407 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 1408 break; 1409 1410 case com.android.internal.R.styleable.TextView_ems: 1411 setEms(a.getInt(attr, -1)); 1412 break; 1413 1414 case com.android.internal.R.styleable.TextView_width: 1415 setWidth(a.getDimensionPixelSize(attr, -1)); 1416 break; 1417 1418 case com.android.internal.R.styleable.TextView_minEms: 1419 setMinEms(a.getInt(attr, -1)); 1420 break; 1421 1422 case com.android.internal.R.styleable.TextView_minWidth: 1423 setMinWidth(a.getDimensionPixelSize(attr, -1)); 1424 break; 1425 1426 case com.android.internal.R.styleable.TextView_gravity: 1427 setGravity(a.getInt(attr, -1)); 1428 break; 1429 1430 case com.android.internal.R.styleable.TextView_hint: 1431 mHintId = a.getResourceId(attr, Resources.ID_NULL); 1432 hint = a.getText(attr); 1433 break; 1434 1435 case com.android.internal.R.styleable.TextView_text: 1436 textIsSetFromXml = true; 1437 mTextId = a.getResourceId(attr, Resources.ID_NULL); 1438 text = a.getText(attr); 1439 break; 1440 1441 case com.android.internal.R.styleable.TextView_scrollHorizontally: 1442 if (a.getBoolean(attr, false)) { 1443 setHorizontallyScrolling(true); 1444 } 1445 break; 1446 1447 case com.android.internal.R.styleable.TextView_singleLine: 1448 singleLine = a.getBoolean(attr, singleLine); 1449 break; 1450 1451 case com.android.internal.R.styleable.TextView_ellipsize: 1452 ellipsize = a.getInt(attr, ellipsize); 1453 break; 1454 1455 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 1456 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 1457 break; 1458 1459 case com.android.internal.R.styleable.TextView_includeFontPadding: 1460 if (!a.getBoolean(attr, true)) { 1461 setIncludeFontPadding(false); 1462 } 1463 break; 1464 1465 case com.android.internal.R.styleable.TextView_cursorVisible: 1466 if (!a.getBoolean(attr, true)) { 1467 setCursorVisible(false); 1468 } 1469 break; 1470 1471 case com.android.internal.R.styleable.TextView_maxLength: 1472 maxlength = a.getInt(attr, -1); 1473 break; 1474 1475 case com.android.internal.R.styleable.TextView_textScaleX: 1476 setTextScaleX(a.getFloat(attr, 1.0f)); 1477 break; 1478 1479 case com.android.internal.R.styleable.TextView_freezesText: 1480 mFreezesText = a.getBoolean(attr, false); 1481 break; 1482 1483 case com.android.internal.R.styleable.TextView_enabled: 1484 setEnabled(a.getBoolean(attr, isEnabled())); 1485 break; 1486 1487 case com.android.internal.R.styleable.TextView_password: 1488 password = a.getBoolean(attr, password); 1489 break; 1490 1491 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 1492 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 1493 break; 1494 1495 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 1496 mSpacingMult = a.getFloat(attr, mSpacingMult); 1497 break; 1498 1499 case com.android.internal.R.styleable.TextView_inputType: 1500 inputType = a.getInt(attr, EditorInfo.TYPE_NULL); 1501 break; 1502 1503 case com.android.internal.R.styleable.TextView_allowUndo: 1504 createEditorIfNeeded(); 1505 mEditor.mAllowUndo = a.getBoolean(attr, true); 1506 break; 1507 1508 case com.android.internal.R.styleable.TextView_imeOptions: 1509 createEditorIfNeeded(); 1510 mEditor.createInputContentTypeIfNeeded(); 1511 mEditor.mInputContentType.imeOptions = a.getInt(attr, 1512 mEditor.mInputContentType.imeOptions); 1513 break; 1514 1515 case com.android.internal.R.styleable.TextView_imeActionLabel: 1516 createEditorIfNeeded(); 1517 mEditor.createInputContentTypeIfNeeded(); 1518 mEditor.mInputContentType.imeActionLabel = a.getText(attr); 1519 break; 1520 1521 case com.android.internal.R.styleable.TextView_imeActionId: 1522 createEditorIfNeeded(); 1523 mEditor.createInputContentTypeIfNeeded(); 1524 mEditor.mInputContentType.imeActionId = a.getInt(attr, 1525 mEditor.mInputContentType.imeActionId); 1526 break; 1527 1528 case com.android.internal.R.styleable.TextView_privateImeOptions: 1529 setPrivateImeOptions(a.getString(attr)); 1530 break; 1531 1532 case com.android.internal.R.styleable.TextView_editorExtras: 1533 try { 1534 setInputExtras(a.getResourceId(attr, 0)); 1535 } catch (XmlPullParserException e) { 1536 Log.w(LOG_TAG, "Failure reading input extras", e); 1537 } catch (IOException e) { 1538 Log.w(LOG_TAG, "Failure reading input extras", e); 1539 } 1540 break; 1541 1542 case com.android.internal.R.styleable.TextView_textCursorDrawable: 1543 mCursorDrawableRes = a.getResourceId(attr, 0); 1544 break; 1545 1546 case com.android.internal.R.styleable.TextView_textSelectHandleLeft: 1547 mTextSelectHandleLeftRes = a.getResourceId(attr, 0); 1548 break; 1549 1550 case com.android.internal.R.styleable.TextView_textSelectHandleRight: 1551 mTextSelectHandleRightRes = a.getResourceId(attr, 0); 1552 break; 1553 1554 case com.android.internal.R.styleable.TextView_textSelectHandle: 1555 mTextSelectHandleRes = a.getResourceId(attr, 0); 1556 break; 1557 1558 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout: 1559 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0); 1560 break; 1561 1562 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout: 1563 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0); 1564 break; 1565 1566 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle: 1567 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0); 1568 break; 1569 1570 case com.android.internal.R.styleable.TextView_textIsSelectable: 1571 setTextIsSelectable(a.getBoolean(attr, false)); 1572 break; 1573 1574 case com.android.internal.R.styleable.TextView_breakStrategy: 1575 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); 1576 break; 1577 1578 case com.android.internal.R.styleable.TextView_hyphenationFrequency: 1579 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); 1580 break; 1581 1582 case com.android.internal.R.styleable.TextView_lineBreakStyle: 1583 mLineBreakStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE); 1584 break; 1585 1586 case com.android.internal.R.styleable.TextView_lineBreakWordStyle: 1587 mLineBreakWordStyle = a.getInt(attr, 1588 LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE); 1589 break; 1590 1591 case com.android.internal.R.styleable.TextView_autoSizeTextType: 1592 mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); 1593 break; 1594 1595 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity: 1596 autoSizeStepGranularityInPx = a.getDimension(attr, 1597 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1598 break; 1599 1600 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize: 1601 autoSizeMinTextSizeInPx = a.getDimension(attr, 1602 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1603 break; 1604 1605 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize: 1606 autoSizeMaxTextSizeInPx = a.getDimension(attr, 1607 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1608 break; 1609 1610 case com.android.internal.R.styleable.TextView_autoSizePresetSizes: 1611 final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0); 1612 if (autoSizeStepSizeArrayResId > 0) { 1613 final TypedArray autoSizePresetTextSizes = a.getResources() 1614 .obtainTypedArray(autoSizeStepSizeArrayResId); 1615 setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes); 1616 autoSizePresetTextSizes.recycle(); 1617 } 1618 break; 1619 case com.android.internal.R.styleable.TextView_justificationMode: 1620 mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE); 1621 break; 1622 1623 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight: 1624 firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1); 1625 break; 1626 1627 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight: 1628 lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1); 1629 break; 1630 1631 case com.android.internal.R.styleable.TextView_lineHeight: 1632 TypedValue peekValue = a.peekValue(attr); 1633 if (peekValue != null && peekValue.type == TypedValue.TYPE_DIMENSION) { 1634 lineHeightUnit = peekValue.getComplexUnit(); 1635 lineHeight = TypedValue.complexToFloat(peekValue.data); 1636 } else { 1637 lineHeight = a.getDimensionPixelSize(attr, -1); 1638 } 1639 break; 1640 case com.android.internal.R.styleable.TextView_useBoundsForWidth: 1641 mUseBoundsForWidth = a.getBoolean(attr, false); 1642 hasUseBoundForWidthValue = true; 1643 break; 1644 case com.android.internal.R.styleable 1645 .TextView_shiftDrawingOffsetForStartOverhang: 1646 mShiftDrawingOffsetForStartOverhang = a.getBoolean(attr, false); 1647 break; 1648 case com.android.internal.R.styleable 1649 .TextView_useLocalePreferredLineHeightForMinimum: 1650 mUseLocalePreferredLineHeightForMinimum = a.getBoolean(attr, false); 1651 break; 1652 } 1653 } 1654 1655 a.recycle(); 1656 1657 BufferType bufferType = BufferType.EDITABLE; 1658 1659 final int variation = 1660 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 1661 final boolean passwordInputType = variation 1662 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); 1663 final boolean webPasswordInputType = variation 1664 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD); 1665 final boolean numberPasswordInputType = variation 1666 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 1667 1668 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 1669 mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; 1670 if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) { 1671 mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_ALL; 1672 } else if (CompatChanges.isChangeEnabled(STATICLAYOUT_FALLBACK_LINESPACING)) { 1673 mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; 1674 } else { 1675 mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE; 1676 } 1677 1678 if (!hasUseBoundForWidthValue) { 1679 mUseBoundsForWidth = CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH); 1680 } 1681 1682 // TODO(b/179693024): Use a ChangeId instead. 1683 mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R; 1684 1685 if (inputMethod != null) { 1686 Class<?> c; 1687 1688 try { 1689 c = Class.forName(inputMethod.toString()); 1690 } catch (ClassNotFoundException ex) { 1691 throw new RuntimeException(ex); 1692 } 1693 1694 try { 1695 createEditorIfNeeded(); 1696 mEditor.mKeyListener = (KeyListener) c.newInstance(); 1697 } catch (InstantiationException ex) { 1698 throw new RuntimeException(ex); 1699 } catch (IllegalAccessException ex) { 1700 throw new RuntimeException(ex); 1701 } 1702 try { 1703 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1704 ? inputType 1705 : mEditor.mKeyListener.getInputType(); 1706 } catch (IncompatibleClassChangeError e) { 1707 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1708 } 1709 } else if (digits != null) { 1710 createEditorIfNeeded(); 1711 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString()); 1712 // If no input type was specified, we will default to generic 1713 // text, since we can't tell the IME about the set of digits 1714 // that was selected. 1715 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1716 ? inputType : EditorInfo.TYPE_CLASS_TEXT; 1717 } else if (inputType != EditorInfo.TYPE_NULL) { 1718 setInputType(inputType, true); 1719 // If set, the input type overrides what was set using the deprecated singleLine flag. 1720 singleLine = !isMultilineInputType(inputType); 1721 } else if (phone) { 1722 createEditorIfNeeded(); 1723 mEditor.mKeyListener = DialerKeyListener.getInstance(); 1724 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; 1725 } else if (numeric != 0) { 1726 createEditorIfNeeded(); 1727 mEditor.mKeyListener = DigitsKeyListener.getInstance( 1728 null, // locale 1729 (numeric & SIGNED) != 0, 1730 (numeric & DECIMAL) != 0); 1731 inputType = mEditor.mKeyListener.getInputType(); 1732 mEditor.mInputType = inputType; 1733 } else if (autotext || autocap != -1) { 1734 TextKeyListener.Capitalize cap; 1735 1736 inputType = EditorInfo.TYPE_CLASS_TEXT; 1737 1738 switch (autocap) { 1739 case 1: 1740 cap = TextKeyListener.Capitalize.SENTENCES; 1741 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 1742 break; 1743 1744 case 2: 1745 cap = TextKeyListener.Capitalize.WORDS; 1746 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 1747 break; 1748 1749 case 3: 1750 cap = TextKeyListener.Capitalize.CHARACTERS; 1751 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 1752 break; 1753 1754 default: 1755 cap = TextKeyListener.Capitalize.NONE; 1756 break; 1757 } 1758 1759 createEditorIfNeeded(); 1760 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap); 1761 mEditor.mInputType = inputType; 1762 } else if (editable) { 1763 createEditorIfNeeded(); 1764 mEditor.mKeyListener = TextKeyListener.getInstance(); 1765 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1766 } else if (isTextSelectable()) { 1767 // Prevent text changes from keyboard. 1768 if (mEditor != null) { 1769 mEditor.mKeyListener = null; 1770 mEditor.mInputType = EditorInfo.TYPE_NULL; 1771 } 1772 bufferType = BufferType.SPANNABLE; 1773 // So that selection can be changed using arrow keys and touch is handled. 1774 setMovementMethod(ArrowKeyMovementMethod.getInstance()); 1775 } else { 1776 if (mEditor != null) mEditor.mKeyListener = null; 1777 1778 switch (buffertype) { 1779 case 0: 1780 bufferType = BufferType.NORMAL; 1781 break; 1782 case 1: 1783 bufferType = BufferType.SPANNABLE; 1784 break; 1785 case 2: 1786 bufferType = BufferType.EDITABLE; 1787 break; 1788 } 1789 } 1790 1791 if (mEditor != null) { 1792 mEditor.adjustInputType(password, passwordInputType, webPasswordInputType, 1793 numberPasswordInputType); 1794 } 1795 1796 if (selectallonfocus) { 1797 createEditorIfNeeded(); 1798 mEditor.mSelectAllOnFocus = true; 1799 1800 if (bufferType == BufferType.NORMAL) { 1801 bufferType = BufferType.SPANNABLE; 1802 } 1803 } 1804 1805 // Set up the tint (if needed) before setting the drawables so that it 1806 // gets applied correctly. 1807 if (drawableTint != null || drawableTintMode != null) { 1808 if (mDrawables == null) { 1809 mDrawables = new Drawables(context); 1810 } 1811 if (drawableTint != null) { 1812 mDrawables.mTintList = drawableTint; 1813 mDrawables.mHasTint = true; 1814 } 1815 if (drawableTintMode != null) { 1816 mDrawables.mBlendMode = drawableTintMode; 1817 mDrawables.mHasTintMode = true; 1818 } 1819 } 1820 1821 // This call will save the initial left/right drawables 1822 setCompoundDrawablesWithIntrinsicBounds( 1823 drawableLeft, drawableTop, drawableRight, drawableBottom); 1824 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd); 1825 setCompoundDrawablePadding(drawablePadding); 1826 1827 // Same as setSingleLine(), but make sure the transformation method and the maximum number 1828 // of lines of height are unchanged for multi-line TextViews. 1829 setInputTypeSingleLine(singleLine); 1830 applySingleLine(singleLine, singleLine, singleLine, 1831 // Does not apply automated max length filter since length filter will be resolved 1832 // later in this function. 1833 false 1834 ); 1835 1836 if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) { 1837 ellipsize = ELLIPSIZE_END; 1838 } 1839 1840 switch (ellipsize) { 1841 case ELLIPSIZE_START: 1842 setEllipsize(TextUtils.TruncateAt.START); 1843 break; 1844 case ELLIPSIZE_MIDDLE: 1845 setEllipsize(TextUtils.TruncateAt.MIDDLE); 1846 break; 1847 case ELLIPSIZE_END: 1848 setEllipsize(TextUtils.TruncateAt.END); 1849 break; 1850 case ELLIPSIZE_MARQUEE: 1851 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) { 1852 setHorizontalFadingEdgeEnabled(true); 1853 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 1854 } else { 1855 setHorizontalFadingEdgeEnabled(false); 1856 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 1857 } 1858 setEllipsize(TextUtils.TruncateAt.MARQUEE); 1859 break; 1860 } 1861 1862 final boolean isPassword = password || passwordInputType || webPasswordInputType 1863 || numberPasswordInputType; 1864 final boolean isMonospaceEnforced = isPassword || (mEditor != null 1865 && (mEditor.mInputType 1866 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) 1867 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)); 1868 if (isMonospaceEnforced) { 1869 attributes.mTypefaceIndex = MONOSPACE; 1870 } 1871 1872 mFontWeightAdjustment = getContext().getResources().getConfiguration().fontWeightAdjustment; 1873 applyTextAppearance(attributes); 1874 1875 if (isPassword) { 1876 setTransformationMethod(PasswordTransformationMethod.getInstance()); 1877 } 1878 1879 // For addressing b/145128646 1880 // For the performance reason, we limit characters for single line text field. 1881 if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) { 1882 mSingleLineLengthFilter = new InputFilter.LengthFilter( 1883 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT); 1884 } 1885 1886 if (mSingleLineLengthFilter != null) { 1887 setFilters(new InputFilter[] { mSingleLineLengthFilter }); 1888 } else if (maxlength >= 0) { 1889 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 1890 } else { 1891 setFilters(NO_FILTERS); 1892 } 1893 1894 setText(text, bufferType); 1895 if (mText == null) { 1896 mText = ""; 1897 } 1898 if (mTransformed == null) { 1899 mTransformed = ""; 1900 } 1901 1902 if (textIsSetFromXml) { 1903 mTextSetFromXmlOrResourceId = true; 1904 } 1905 1906 if (hint != null) setHint(hint); 1907 1908 /* 1909 * Views are not normally clickable unless specified to be. 1910 * However, TextViews that have input or movement methods *are* 1911 * clickable by default. By setting clickable here, we implicitly set focusable as well 1912 * if not overridden by the developer. 1913 */ 1914 a = context.obtainStyledAttributes( 1915 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); 1916 boolean canInputOrMove = (mMovement != null || getKeyListener() != null); 1917 boolean clickable = canInputOrMove || isClickable(); 1918 boolean longClickable = canInputOrMove || isLongClickable(); 1919 int focusable = getFocusable(); 1920 boolean isAutoHandwritingEnabled = true; 1921 1922 n = a.getIndexCount(); 1923 for (int i = 0; i < n; i++) { 1924 int attr = a.getIndex(i); 1925 1926 switch (attr) { 1927 case com.android.internal.R.styleable.View_focusable: 1928 TypedValue val = new TypedValue(); 1929 if (a.getValue(attr, val)) { 1930 focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN) 1931 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE) 1932 : val.data; 1933 } 1934 break; 1935 1936 case com.android.internal.R.styleable.View_clickable: 1937 clickable = a.getBoolean(attr, clickable); 1938 break; 1939 1940 case com.android.internal.R.styleable.View_longClickable: 1941 longClickable = a.getBoolean(attr, longClickable); 1942 break; 1943 1944 case com.android.internal.R.styleable.View_autoHandwritingEnabled: 1945 isAutoHandwritingEnabled = a.getBoolean(attr, true); 1946 break; 1947 } 1948 } 1949 a.recycle(); 1950 1951 // Some apps were relying on the undefined behavior of focusable winning over 1952 // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually 1953 // when starting with EditText and setting only focusable=false). To keep those apps from 1954 // breaking, re-apply the focusable attribute here. 1955 if (focusable != getFocusable()) { 1956 setFocusable(focusable); 1957 } 1958 setClickable(clickable); 1959 setLongClickable(longClickable); 1960 setAutoHandwritingEnabled(isAutoHandwritingEnabled); 1961 1962 if (mEditor != null) mEditor.prepareCursorControllers(); 1963 1964 // If not explicitly specified this view is important for accessibility. 1965 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 1966 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 1967 } 1968 1969 if (supportsAutoSizeText()) { 1970 if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 1971 // If uniform auto-size has been specified but preset values have not been set then 1972 // replace the auto-size configuration values that have not been specified with the 1973 // defaults. 1974 if (!mHasPresetAutoSizeValues) { 1975 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1976 1977 if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1978 autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1979 TypedValue.COMPLEX_UNIT_SP, 1980 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1981 displayMetrics); 1982 } 1983 1984 if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1985 autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1986 TypedValue.COMPLEX_UNIT_SP, 1987 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1988 displayMetrics); 1989 } 1990 1991 if (autoSizeStepGranularityInPx 1992 == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1993 autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX; 1994 } 1995 1996 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1997 autoSizeMaxTextSizeInPx, 1998 autoSizeStepGranularityInPx); 1999 } 2000 2001 setupAutoSizeText(); 2002 } 2003 } else { 2004 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 2005 } 2006 2007 if (firstBaselineToTopHeight >= 0) { 2008 setFirstBaselineToTopHeight(firstBaselineToTopHeight); 2009 } 2010 if (lastBaselineToBottomHeight >= 0) { 2011 setLastBaselineToBottomHeight(lastBaselineToBottomHeight); 2012 } 2013 if (lineHeight >= 0) { 2014 if (lineHeightUnit == -1) { 2015 setLineHeightPx(lineHeight); 2016 } else { 2017 setLineHeight(lineHeightUnit, lineHeight); 2018 } 2019 } 2020 } 2021 2022 // Update mText and mPrecomputed setTextInternal(@ullable CharSequence text)2023 private void setTextInternal(@Nullable CharSequence text) { 2024 mText = text; 2025 mSpannable = (text instanceof Spannable) ? (Spannable) text : null; 2026 mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 2027 } 2028 2029 /** 2030 * Specify whether this widget should automatically scale the text to try to perfectly fit 2031 * within the layout bounds by using the default auto-size configuration. 2032 * 2033 * @param autoSizeTextType the type of auto-size. Must be one of 2034 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 2035 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 2036 * 2037 * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above. 2038 * 2039 * @attr ref android.R.styleable#TextView_autoSizeTextType 2040 * 2041 * @see #getAutoSizeTextType() 2042 */ setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)2043 public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) { 2044 if (supportsAutoSizeText()) { 2045 switch (autoSizeTextType) { 2046 case AUTO_SIZE_TEXT_TYPE_NONE: 2047 clearAutoSizeConfiguration(); 2048 break; 2049 case AUTO_SIZE_TEXT_TYPE_UNIFORM: 2050 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 2051 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 2052 TypedValue.COMPLEX_UNIT_SP, 2053 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 2054 displayMetrics); 2055 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 2056 TypedValue.COMPLEX_UNIT_SP, 2057 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 2058 displayMetrics); 2059 2060 validateAndSetAutoSizeTextTypeUniformConfiguration( 2061 autoSizeMinTextSizeInPx, 2062 autoSizeMaxTextSizeInPx, 2063 DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX); 2064 if (setupAutoSizeText()) { 2065 autoSizeText(); 2066 invalidate(); 2067 } 2068 break; 2069 default: 2070 throw new IllegalArgumentException( 2071 "Unknown auto-size text type: " + autoSizeTextType); 2072 } 2073 } 2074 } 2075 2076 /** 2077 * Specify whether this widget should automatically scale the text to try to perfectly fit 2078 * within the layout bounds. If all the configuration params are valid the type of auto-size is 2079 * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 2080 * 2081 * @param autoSizeMinTextSize the minimum text size available for auto-size 2082 * @param autoSizeMaxTextSize the maximum text size available for auto-size 2083 * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with 2084 * the minimum and maximum text size in order to build the set of 2085 * text sizes the system uses to choose from when auto-sizing 2086 * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the 2087 * possible dimension units 2088 * 2089 * @throws IllegalArgumentException if any of the configuration params are invalid. 2090 * 2091 * @attr ref android.R.styleable#TextView_autoSizeTextType 2092 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 2093 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 2094 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 2095 * 2096 * @see #setAutoSizeTextTypeWithDefaults(int) 2097 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2098 * @see #getAutoSizeMinTextSize() 2099 * @see #getAutoSizeMaxTextSize() 2100 * @see #getAutoSizeStepGranularity() 2101 * @see #getAutoSizeTextAvailableSizes() 2102 */ setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)2103 public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, 2104 int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) { 2105 if (supportsAutoSizeText()) { 2106 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 2107 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 2108 unit, autoSizeMinTextSize, displayMetrics); 2109 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 2110 unit, autoSizeMaxTextSize, displayMetrics); 2111 final float autoSizeStepGranularityInPx = TypedValue.applyDimension( 2112 unit, autoSizeStepGranularity, displayMetrics); 2113 2114 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 2115 autoSizeMaxTextSizeInPx, 2116 autoSizeStepGranularityInPx); 2117 2118 if (setupAutoSizeText()) { 2119 autoSizeText(); 2120 invalidate(); 2121 } 2122 } 2123 } 2124 2125 /** 2126 * Specify whether this widget should automatically scale the text to try to perfectly fit 2127 * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid 2128 * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 2129 * 2130 * @param presetSizes an {@code int} array of sizes in pixels 2131 * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for 2132 * the possible dimension units 2133 * 2134 * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid. 2135 * 2136 * @attr ref android.R.styleable#TextView_autoSizeTextType 2137 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 2138 * 2139 * @see #setAutoSizeTextTypeWithDefaults(int) 2140 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2141 * @see #getAutoSizeMinTextSize() 2142 * @see #getAutoSizeMaxTextSize() 2143 * @see #getAutoSizeTextAvailableSizes() 2144 */ setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)2145 public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) { 2146 if (supportsAutoSizeText()) { 2147 final int presetSizesLength = presetSizes.length; 2148 if (presetSizesLength > 0) { 2149 int[] presetSizesInPx = new int[presetSizesLength]; 2150 2151 if (unit == TypedValue.COMPLEX_UNIT_PX) { 2152 presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength); 2153 } else { 2154 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 2155 // Convert all to sizes to pixels. 2156 for (int i = 0; i < presetSizesLength; i++) { 2157 presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit, 2158 presetSizes[i], displayMetrics)); 2159 } 2160 } 2161 2162 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx); 2163 if (!setupAutoSizeUniformPresetSizesConfiguration()) { 2164 throw new IllegalArgumentException("None of the preset sizes is valid: " 2165 + Arrays.toString(presetSizes)); 2166 } 2167 } else { 2168 mHasPresetAutoSizeValues = false; 2169 } 2170 2171 if (setupAutoSizeText()) { 2172 autoSizeText(); 2173 invalidate(); 2174 } 2175 } 2176 } 2177 2178 /** 2179 * Returns the type of auto-size set for this widget. 2180 * 2181 * @return an {@code int} corresponding to one of the auto-size types: 2182 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 2183 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 2184 * 2185 * @attr ref android.R.styleable#TextView_autoSizeTextType 2186 * 2187 * @see #setAutoSizeTextTypeWithDefaults(int) 2188 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2189 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2190 */ 2191 @InspectableProperty(enumMapping = { 2192 @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE), 2193 @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM) 2194 }) 2195 @AutoSizeTextType getAutoSizeTextType()2196 public int getAutoSizeTextType() { 2197 return mAutoSizeTextType; 2198 } 2199 2200 /** 2201 * @return the current auto-size step granularity in pixels. 2202 * 2203 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 2204 * 2205 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2206 */ 2207 @InspectableProperty getAutoSizeStepGranularity()2208 public int getAutoSizeStepGranularity() { 2209 return Math.round(mAutoSizeStepGranularityInPx); 2210 } 2211 2212 /** 2213 * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that 2214 * if auto-size has not been configured this function returns {@code -1}. 2215 * 2216 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 2217 * 2218 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2219 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2220 */ 2221 @InspectableProperty getAutoSizeMinTextSize()2222 public int getAutoSizeMinTextSize() { 2223 return Math.round(mAutoSizeMinTextSizeInPx); 2224 } 2225 2226 /** 2227 * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that 2228 * if auto-size has not been configured this function returns {@code -1}. 2229 * 2230 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 2231 * 2232 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2233 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2234 */ 2235 @InspectableProperty getAutoSizeMaxTextSize()2236 public int getAutoSizeMaxTextSize() { 2237 return Math.round(mAutoSizeMaxTextSizeInPx); 2238 } 2239 2240 /** 2241 * @return the current auto-size {@code int} sizes array (in pixels). 2242 * 2243 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2244 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2245 */ getAutoSizeTextAvailableSizes()2246 public int[] getAutoSizeTextAvailableSizes() { 2247 return mAutoSizeTextSizesInPx; 2248 } 2249 setupAutoSizeUniformPresetSizes(TypedArray textSizes)2250 private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) { 2251 final int textSizesLength = textSizes.length(); 2252 final int[] parsedSizes = new int[textSizesLength]; 2253 2254 if (textSizesLength > 0) { 2255 for (int i = 0; i < textSizesLength; i++) { 2256 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1); 2257 } 2258 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes); 2259 setupAutoSizeUniformPresetSizesConfiguration(); 2260 } 2261 } 2262 setupAutoSizeUniformPresetSizesConfiguration()2263 private boolean setupAutoSizeUniformPresetSizesConfiguration() { 2264 final int sizesLength = mAutoSizeTextSizesInPx.length; 2265 mHasPresetAutoSizeValues = sizesLength > 0; 2266 if (mHasPresetAutoSizeValues) { 2267 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2268 mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0]; 2269 mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1]; 2270 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2271 } 2272 return mHasPresetAutoSizeValues; 2273 } 2274 2275 /** 2276 * If all params are valid then save the auto-size configuration. 2277 * 2278 * @throws IllegalArgumentException if any of the params are invalid 2279 */ validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)2280 private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, 2281 float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) { 2282 // First validate. 2283 if (autoSizeMinTextSizeInPx <= 0) { 2284 throw new IllegalArgumentException("Minimum auto-size text size (" 2285 + autoSizeMinTextSizeInPx + "px) is less or equal to (0px)"); 2286 } 2287 2288 if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) { 2289 throw new IllegalArgumentException("Maximum auto-size text size (" 2290 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size " 2291 + "text size (" + autoSizeMinTextSizeInPx + "px)"); 2292 } 2293 2294 if (autoSizeStepGranularityInPx <= 0) { 2295 throw new IllegalArgumentException("The auto-size step granularity (" 2296 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)"); 2297 } 2298 2299 // All good, persist the configuration. 2300 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2301 mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx; 2302 mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx; 2303 mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx; 2304 mHasPresetAutoSizeValues = false; 2305 } 2306 clearAutoSizeConfiguration()2307 private void clearAutoSizeConfiguration() { 2308 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 2309 mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2310 mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2311 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2312 mAutoSizeTextSizesInPx = EmptyArray.INT; 2313 mNeedsAutoSizeText = false; 2314 } 2315 2316 // Returns distinct sorted positive values. cleanupAutoSizePresetSizes(int[] presetValues)2317 private int[] cleanupAutoSizePresetSizes(int[] presetValues) { 2318 final int presetValuesLength = presetValues.length; 2319 if (presetValuesLength == 0) { 2320 return presetValues; 2321 } 2322 Arrays.sort(presetValues); 2323 2324 final IntArray uniqueValidSizes = new IntArray(); 2325 for (int i = 0; i < presetValuesLength; i++) { 2326 final int currentPresetValue = presetValues[i]; 2327 2328 if (currentPresetValue > 0 2329 && uniqueValidSizes.binarySearch(currentPresetValue) < 0) { 2330 uniqueValidSizes.add(currentPresetValue); 2331 } 2332 } 2333 2334 return presetValuesLength == uniqueValidSizes.size() 2335 ? presetValues 2336 : uniqueValidSizes.toArray(); 2337 } 2338 setupAutoSizeText()2339 private boolean setupAutoSizeText() { 2340 if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 2341 // Calculate the sizes set based on minimum size, maximum size and step size if we do 2342 // not have a predefined set of sizes or if the current sizes array is empty. 2343 if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) { 2344 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx 2345 - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1; 2346 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength]; 2347 for (int i = 0; i < autoSizeValuesLength; i++) { 2348 autoSizeTextSizesInPx[i] = Math.round( 2349 mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx)); 2350 } 2351 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx); 2352 } 2353 2354 mNeedsAutoSizeText = true; 2355 } else { 2356 mNeedsAutoSizeText = false; 2357 } 2358 2359 return mNeedsAutoSizeText; 2360 } 2361 parseDimensionArray(TypedArray dimens)2362 private int[] parseDimensionArray(TypedArray dimens) { 2363 if (dimens == null) { 2364 return null; 2365 } 2366 int[] result = new int[dimens.length()]; 2367 for (int i = 0; i < result.length; i++) { 2368 result[i] = dimens.getDimensionPixelSize(i, 0); 2369 } 2370 return result; 2371 } 2372 2373 /** 2374 * @hide 2375 */ 2376 @TestApi 2377 @Override onActivityResult(int requestCode, int resultCode, @Nullable Intent data)2378 public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 2379 if (requestCode == PROCESS_TEXT_REQUEST_CODE) { 2380 if (resultCode == Activity.RESULT_OK && data != null) { 2381 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT); 2382 if (result != null) { 2383 if (isTextEditable()) { 2384 ClipData clip = ClipData.newPlainText("", result); 2385 ContentInfo payload = 2386 new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build(); 2387 performReceiveContent(payload); 2388 if (mEditor != null) { 2389 mEditor.refreshTextActionMode(); 2390 } 2391 } else { 2392 if (result.length() > 0) { 2393 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG) 2394 .show(); 2395 } 2396 } 2397 } 2398 } else if (mSpannable != null) { 2399 // Reset the selection. 2400 Selection.setSelection(mSpannable, getSelectionEnd()); 2401 } 2402 } 2403 } 2404 2405 /** 2406 * Sets the Typeface taking into account the given attributes. 2407 * 2408 * @param typeface a typeface 2409 * @param familyName family name string, e.g. "serif" 2410 * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF. 2411 * @param style a typeface style 2412 * @param weight a weight value for the Typeface or {@code FontStyle.FONT_WEIGHT_UNSPECIFIED} 2413 * if not specified. 2414 */ 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)2415 private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName, 2416 @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, 2417 @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) 2418 int weight) { 2419 if (typeface == null && familyName != null) { 2420 // Lookup normal Typeface from system font map. 2421 final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL); 2422 resolveStyleAndSetTypeface(normalTypeface, style, weight); 2423 } else if (typeface != null) { 2424 resolveStyleAndSetTypeface(typeface, style, weight); 2425 } else { // both typeface and familyName is null. 2426 switch (typefaceIndex) { 2427 case SANS: 2428 resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight); 2429 break; 2430 case SERIF: 2431 resolveStyleAndSetTypeface(Typeface.SERIF, style, weight); 2432 break; 2433 case MONOSPACE: 2434 resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight); 2435 break; 2436 case DEFAULT_TYPEFACE: 2437 default: 2438 resolveStyleAndSetTypeface(null, style, weight); 2439 break; 2440 } 2441 } 2442 } 2443 resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) int weight)2444 private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style, 2445 @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) 2446 int weight) { 2447 if (weight >= 0) { 2448 weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight); 2449 final boolean italic = (style & Typeface.ITALIC) != 0; 2450 setTypeface(Typeface.create(typeface, weight, italic)); 2451 } else { 2452 setTypeface(typeface, style); 2453 } 2454 } 2455 setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2456 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) { 2457 boolean hasRelativeDrawables = (start != null) || (end != null); 2458 if (hasRelativeDrawables) { 2459 Drawables dr = mDrawables; 2460 if (dr == null) { 2461 mDrawables = dr = new Drawables(getContext()); 2462 } 2463 mDrawables.mOverride = true; 2464 final Rect compoundRect = dr.mCompoundRect; 2465 int[] state = getDrawableState(); 2466 if (start != null) { 2467 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 2468 start.setState(state); 2469 start.copyBounds(compoundRect); 2470 start.setCallback(this); 2471 2472 dr.mDrawableStart = start; 2473 dr.mDrawableSizeStart = compoundRect.width(); 2474 dr.mDrawableHeightStart = compoundRect.height(); 2475 } else { 2476 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2477 } 2478 if (end != null) { 2479 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 2480 end.setState(state); 2481 end.copyBounds(compoundRect); 2482 end.setCallback(this); 2483 2484 dr.mDrawableEnd = end; 2485 dr.mDrawableSizeEnd = compoundRect.width(); 2486 dr.mDrawableHeightEnd = compoundRect.height(); 2487 } else { 2488 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2489 } 2490 resetResolvedDrawables(); 2491 resolveDrawables(); 2492 applyCompoundDrawableTint(); 2493 } 2494 } 2495 2496 @android.view.RemotableViewMethod 2497 @Override setEnabled(boolean enabled)2498 public void setEnabled(boolean enabled) { 2499 if (enabled == isEnabled()) { 2500 return; 2501 } 2502 2503 if (!enabled) { 2504 // Hide the soft input if the currently active TextView is disabled 2505 InputMethodManager imm = getInputMethodManager(); 2506 if (imm != null) { 2507 imm.hideSoftInputFromView(this, 0); 2508 } 2509 } 2510 2511 super.setEnabled(enabled); 2512 2513 if (enabled) { 2514 // Make sure IME is updated with current editor info. 2515 InputMethodManager imm = getInputMethodManager(); 2516 if (imm != null) imm.restartInput(this); 2517 } 2518 2519 // Will change text color 2520 if (mEditor != null) { 2521 mEditor.invalidateTextDisplayList(); 2522 mEditor.prepareCursorControllers(); 2523 2524 // start or stop the cursor blinking as appropriate 2525 mEditor.makeBlink(); 2526 } 2527 } 2528 2529 /** 2530 * Sets the typeface and style in which the text should be displayed, 2531 * and turns on the fake bold and italic bits in the Paint if the 2532 * Typeface that you provided does not have all the bits in the 2533 * style that you specified. 2534 * 2535 * @attr ref android.R.styleable#TextView_typeface 2536 * @attr ref android.R.styleable#TextView_textStyle 2537 */ setTypeface(@ullable Typeface tf, @Typeface.Style int style)2538 public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) { 2539 if (style > 0) { 2540 if (tf == null) { 2541 tf = Typeface.defaultFromStyle(style); 2542 } else { 2543 tf = Typeface.create(tf, style); 2544 } 2545 2546 setTypeface(tf); 2547 // now compute what (if any) algorithmic styling is needed 2548 int typefaceStyle = tf != null ? tf.getStyle() : 0; 2549 int need = style & ~typefaceStyle; 2550 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 2551 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 2552 } else { 2553 mTextPaint.setFakeBoldText(false); 2554 mTextPaint.setTextSkewX(0); 2555 setTypeface(tf); 2556 } 2557 } 2558 2559 /** 2560 * Subclasses override this to specify that they have a KeyListener 2561 * by default even if not specifically called for in the XML options. 2562 */ getDefaultEditable()2563 protected boolean getDefaultEditable() { 2564 return false; 2565 } 2566 2567 /** 2568 * Subclasses override this to specify a default movement method. 2569 */ getDefaultMovementMethod()2570 protected MovementMethod getDefaultMovementMethod() { 2571 return null; 2572 } 2573 2574 /** 2575 * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called 2576 * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE} 2577 * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast 2578 * the return value from this method to Spannable or Editable, respectively. 2579 * 2580 * <p>The content of the return value should not be modified. If you want a modifiable one, you 2581 * should make your own copy first.</p> 2582 * 2583 * @return The text displayed by the text view. 2584 * @attr ref android.R.styleable#TextView_text 2585 */ 2586 @ViewDebug.CapturedViewProperty 2587 @InspectableProperty getText()2588 public CharSequence getText() { 2589 if (mUseTextPaddingForUiTranslation) { 2590 ViewTranslationCallback callback = getViewTranslationCallback(); 2591 if (callback != null && callback instanceof TextViewTranslationCallback) { 2592 TextViewTranslationCallback defaultCallback = 2593 (TextViewTranslationCallback) callback; 2594 if (defaultCallback.isTextPaddingEnabled() 2595 && defaultCallback.isShowingTranslation()) { 2596 return defaultCallback.getPaddedText(mText, mTransformed); 2597 } 2598 } 2599 } 2600 return mText; 2601 } 2602 2603 /** 2604 * Returns the length, in characters, of the text managed by this TextView 2605 * @return The length of the text managed by the TextView in characters. 2606 */ length()2607 public int length() { 2608 return mText.length(); 2609 } 2610 2611 /** 2612 * Return the text that TextView is displaying as an Editable object. If the text is not 2613 * editable, null is returned. 2614 * 2615 * @see #getText 2616 */ getEditableText()2617 public Editable getEditableText() { 2618 return (mText instanceof Editable) ? (Editable) mText : null; 2619 } 2620 2621 /** 2622 * @hide 2623 */ 2624 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) getTransformed()2625 public CharSequence getTransformed() { 2626 return mTransformed; 2627 } 2628 2629 /** 2630 * Gets the vertical distance between lines of text, in pixels. 2631 * Note that markup within the text can cause individual lines 2632 * to be taller or shorter than this height, and the layout may 2633 * contain additional first-or last-line padding. 2634 * @return The height of one standard line in pixels. 2635 */ 2636 @InspectableProperty getLineHeight()2637 public int getLineHeight() { 2638 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd); 2639 } 2640 2641 /** 2642 * Gets the {@link android.text.Layout} that is currently being used to display the text. 2643 * This value can be null if the text or width has recently changed. 2644 * @return The Layout that is currently being used to display the text. 2645 */ getLayout()2646 public final Layout getLayout() { 2647 return mLayout; 2648 } 2649 2650 /** 2651 * @return the {@link android.text.Layout} that is currently being used to 2652 * display the hint text. This can be null. 2653 */ 2654 @UnsupportedAppUsage getHintLayout()2655 final Layout getHintLayout() { 2656 return mHintLayout; 2657 } 2658 2659 /** 2660 * Retrieve the {@link android.content.UndoManager} that is currently associated 2661 * with this TextView. By default there is no associated UndoManager, so null 2662 * is returned. One can be associated with the TextView through 2663 * {@link #setUndoManager(android.content.UndoManager, String)} 2664 * 2665 * @hide 2666 */ getUndoManager()2667 public final UndoManager getUndoManager() { 2668 // TODO: Consider supporting a global undo manager. 2669 throw new UnsupportedOperationException("not implemented"); 2670 } 2671 2672 2673 /** 2674 * @hide 2675 */ 2676 @VisibleForTesting getEditorForTesting()2677 public final Editor getEditorForTesting() { 2678 return mEditor; 2679 } 2680 2681 /** 2682 * Associate an {@link android.content.UndoManager} with this TextView. Once 2683 * done, all edit operations on the TextView will result in appropriate 2684 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's 2685 * stack. 2686 * 2687 * @param undoManager The {@link android.content.UndoManager} to associate with 2688 * this TextView, or null to clear any existing association. 2689 * @param tag String tag identifying this particular TextView owner in the 2690 * UndoManager. This is used to keep the correct association with the 2691 * {@link android.content.UndoOwner} of any operations inside of the UndoManager. 2692 * 2693 * @hide 2694 */ setUndoManager(UndoManager undoManager, String tag)2695 public final void setUndoManager(UndoManager undoManager, String tag) { 2696 // TODO: Consider supporting a global undo manager. An implementation will need to: 2697 // * createEditorIfNeeded() 2698 // * Promote to BufferType.EDITABLE if needed. 2699 // * Update the UndoManager and UndoOwner. 2700 // Likewise it will need to be able to restore the default UndoManager. 2701 throw new UnsupportedOperationException("not implemented"); 2702 } 2703 2704 /** 2705 * Gets the current {@link KeyListener} for the TextView. 2706 * This will frequently be null for non-EditText TextViews. 2707 * @return the current key listener for this TextView. 2708 * 2709 * @attr ref android.R.styleable#TextView_numeric 2710 * @attr ref android.R.styleable#TextView_digits 2711 * @attr ref android.R.styleable#TextView_phoneNumber 2712 * @attr ref android.R.styleable#TextView_inputMethod 2713 * @attr ref android.R.styleable#TextView_capitalize 2714 * @attr ref android.R.styleable#TextView_autoText 2715 */ getKeyListener()2716 public final KeyListener getKeyListener() { 2717 return mEditor == null ? null : mEditor.mKeyListener; 2718 } 2719 2720 /** 2721 * Sets the key listener to be used with this TextView. This can be null 2722 * to disallow user input. Note that this method has significant and 2723 * subtle interactions with soft keyboards and other input method: 2724 * see {@link KeyListener#getInputType() KeyListener.getInputType()} 2725 * for important details. Calling this method will replace the current 2726 * content type of the text view with the content type returned by the 2727 * key listener. 2728 * <p> 2729 * Be warned that if you want a TextView with a key listener or movement 2730 * method not to be focusable, or if you want a TextView without a 2731 * key listener or movement method to be focusable, you must call 2732 * {@link #setFocusable} again after calling this to get the focusability 2733 * back the way you want it. 2734 * 2735 * @attr ref android.R.styleable#TextView_numeric 2736 * @attr ref android.R.styleable#TextView_digits 2737 * @attr ref android.R.styleable#TextView_phoneNumber 2738 * @attr ref android.R.styleable#TextView_inputMethod 2739 * @attr ref android.R.styleable#TextView_capitalize 2740 * @attr ref android.R.styleable#TextView_autoText 2741 */ setKeyListener(KeyListener input)2742 public void setKeyListener(KeyListener input) { 2743 mListenerChanged = true; 2744 setKeyListenerOnly(input); 2745 fixFocusableAndClickableSettings(); 2746 2747 if (input != null) { 2748 createEditorIfNeeded(); 2749 setInputTypeFromEditor(); 2750 } else { 2751 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL; 2752 } 2753 2754 InputMethodManager imm = getInputMethodManager(); 2755 if (imm != null) imm.restartInput(this); 2756 2757 ensureEditorFocusedNotifiedToHandwritingInitiator(); 2758 } 2759 setInputTypeFromEditor()2760 private void setInputTypeFromEditor() { 2761 try { 2762 mEditor.mInputType = mEditor.mKeyListener.getInputType(); 2763 } catch (IncompatibleClassChangeError e) { 2764 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 2765 } 2766 // Change inputType, without affecting transformation. 2767 // No need to applySingleLine since mSingleLine is unchanged. 2768 setInputTypeSingleLine(mSingleLine); 2769 } 2770 setKeyListenerOnly(KeyListener input)2771 private void setKeyListenerOnly(KeyListener input) { 2772 if (mEditor == null && input == null) return; // null is the default value 2773 2774 createEditorIfNeeded(); 2775 if (mEditor.mKeyListener != input) { 2776 mEditor.mKeyListener = input; 2777 if (input != null && !(mText instanceof Editable)) { 2778 setText(mText); 2779 } 2780 2781 setFilters((Editable) mText, mFilters); 2782 } 2783 } 2784 2785 /** 2786 * Gets the {@link android.text.method.MovementMethod} being used for this TextView, 2787 * which provides positioning, scrolling, and text selection functionality. 2788 * This will frequently be null for non-EditText TextViews. 2789 * @return the movement method being used for this TextView. 2790 * @see android.text.method.MovementMethod 2791 */ getMovementMethod()2792 public final MovementMethod getMovementMethod() { 2793 return mMovement; 2794 } 2795 2796 /** 2797 * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement 2798 * for this TextView. This can be null to disallow using the arrow keys to move the 2799 * cursor or scroll the view. 2800 * <p> 2801 * Be warned that if you want a TextView with a key listener or movement 2802 * method not to be focusable, or if you want a TextView without a 2803 * key listener or movement method to be focusable, you must call 2804 * {@link #setFocusable} again after calling this to get the focusability 2805 * back the way you want it. 2806 */ setMovementMethod(MovementMethod movement)2807 public final void setMovementMethod(MovementMethod movement) { 2808 if (mMovement != movement) { 2809 mMovement = movement; 2810 2811 if (movement != null && mSpannable == null) { 2812 setText(mText); 2813 } 2814 2815 fixFocusableAndClickableSettings(); 2816 2817 // SelectionModifierCursorController depends on textCanBeSelected, which depends on 2818 // mMovement 2819 if (mEditor != null) mEditor.prepareCursorControllers(); 2820 } 2821 } 2822 fixFocusableAndClickableSettings()2823 private void fixFocusableAndClickableSettings() { 2824 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) { 2825 setFocusable(FOCUSABLE); 2826 setClickable(true); 2827 setLongClickable(true); 2828 } else { 2829 setFocusable(FOCUSABLE_AUTO); 2830 setClickable(false); 2831 setLongClickable(false); 2832 } 2833 } 2834 2835 /** 2836 * Gets the current {@link android.text.method.TransformationMethod} for the TextView. 2837 * This is frequently null, except for single-line and password fields. 2838 * @return the current transformation method for this TextView. 2839 * 2840 * @attr ref android.R.styleable#TextView_password 2841 * @attr ref android.R.styleable#TextView_singleLine 2842 */ getTransformationMethod()2843 public final TransformationMethod getTransformationMethod() { 2844 return mTransformation; 2845 } 2846 2847 /** 2848 * Sets the transformation that is applied to the text that this 2849 * TextView is displaying. 2850 * 2851 * @attr ref android.R.styleable#TextView_password 2852 * @attr ref android.R.styleable#TextView_singleLine 2853 */ setTransformationMethod(TransformationMethod method)2854 public final void setTransformationMethod(TransformationMethod method) { 2855 if (mEditor != null) { 2856 mEditor.setTransformationMethod(method); 2857 } else { 2858 setTransformationMethodInternal(method, /* updateText */ true); 2859 } 2860 } 2861 2862 /** 2863 * Set the transformation that is applied to the text that this TextView is displaying, 2864 * optionally call the setText. 2865 * @param method the new transformation method to be set. 2866 * @param updateText whether the call {@link #setText} which will update the TextView to display 2867 * the new content. This method is helpful when updating 2868 * {@link TransformationMethod} inside {@link #setText}. It should only be 2869 * false if text will be updated immediately after this call, otherwise the 2870 * TextView will enter an inconsistent state. 2871 */ setTransformationMethodInternal(@ullable TransformationMethod method, boolean updateText)2872 void setTransformationMethodInternal(@Nullable TransformationMethod method, 2873 boolean updateText) { 2874 if (method == mTransformation) { 2875 // Avoid the setText() below if the transformation is 2876 // the same. 2877 return; 2878 } 2879 if (mTransformation != null) { 2880 if (mSpannable != null) { 2881 mSpannable.removeSpan(mTransformation); 2882 } 2883 } 2884 2885 mTransformation = method; 2886 2887 if (method instanceof TransformationMethod2) { 2888 TransformationMethod2 method2 = (TransformationMethod2) method; 2889 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable); 2890 method2.setLengthChangesAllowed(mAllowTransformationLengthChange); 2891 } else { 2892 mAllowTransformationLengthChange = false; 2893 } 2894 2895 if (updateText) { 2896 if (Flags.insertModeNotUpdateSelection()) { 2897 // Update the transformation text. 2898 if (mTransformation == null) { 2899 mTransformed = mText; 2900 } else { 2901 mTransformed = mTransformation.getTransformation(mText, this); 2902 } 2903 if (mTransformed == null) { 2904 // Should not happen if the transformation method follows the non-null 2905 // postcondition. 2906 mTransformed = ""; 2907 } 2908 final boolean isOffsetMapping = mTransformed instanceof OffsetMapping; 2909 2910 // If the mText is a Spannable and the new TransformationMethod needs to listen to 2911 // its updates, apply the watcher on it. 2912 if (mTransformation != null && mText instanceof Spannable 2913 && (!mAllowTransformationLengthChange || isOffsetMapping)) { 2914 Spannable sp = (Spannable) mText; 2915 final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0; 2916 sp.setSpan(mTransformation, 0, mText.length(), 2917 Spanned.SPAN_INCLUSIVE_INCLUSIVE 2918 | (priority << Spanned.SPAN_PRIORITY_SHIFT)); 2919 } 2920 if (mLayout != null) { 2921 nullLayouts(); 2922 requestLayout(); 2923 invalidate(); 2924 } 2925 } else { 2926 setText(mText); 2927 } 2928 } 2929 2930 if (hasPasswordTransformationMethod()) { 2931 notifyViewAccessibilityStateChangedIfNeeded( 2932 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 2933 } 2934 2935 // PasswordTransformationMethod always have LTR text direction heuristics returned by 2936 // getTextDirectionHeuristic, needs reset 2937 mTextDir = getTextDirectionHeuristic(); 2938 } 2939 2940 /** 2941 * Returns the top padding of the view, plus space for the top 2942 * Drawable if any. 2943 */ getCompoundPaddingTop()2944 public int getCompoundPaddingTop() { 2945 final Drawables dr = mDrawables; 2946 if (dr == null || dr.mShowing[Drawables.TOP] == null) { 2947 return mPaddingTop; 2948 } else { 2949 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 2950 } 2951 } 2952 2953 /** 2954 * Returns the bottom padding of the view, plus space for the bottom 2955 * Drawable if any. 2956 */ getCompoundPaddingBottom()2957 public int getCompoundPaddingBottom() { 2958 final Drawables dr = mDrawables; 2959 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) { 2960 return mPaddingBottom; 2961 } else { 2962 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 2963 } 2964 } 2965 2966 /** 2967 * Returns the left padding of the view, plus space for the left 2968 * Drawable if any. 2969 */ getCompoundPaddingLeft()2970 public int getCompoundPaddingLeft() { 2971 final Drawables dr = mDrawables; 2972 if (dr == null || dr.mShowing[Drawables.LEFT] == null) { 2973 return mPaddingLeft; 2974 } else { 2975 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 2976 } 2977 } 2978 2979 /** 2980 * Returns the right padding of the view, plus space for the right 2981 * Drawable if any. 2982 */ getCompoundPaddingRight()2983 public int getCompoundPaddingRight() { 2984 final Drawables dr = mDrawables; 2985 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) { 2986 return mPaddingRight; 2987 } else { 2988 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 2989 } 2990 } 2991 2992 /** 2993 * Returns the start padding of the view, plus space for the start 2994 * Drawable if any. 2995 */ getCompoundPaddingStart()2996 public int getCompoundPaddingStart() { 2997 resolveDrawables(); 2998 switch(getLayoutDirection()) { 2999 default: 3000 case LAYOUT_DIRECTION_LTR: 3001 return getCompoundPaddingLeft(); 3002 case LAYOUT_DIRECTION_RTL: 3003 return getCompoundPaddingRight(); 3004 } 3005 } 3006 3007 /** 3008 * Returns the end padding of the view, plus space for the end 3009 * Drawable if any. 3010 */ getCompoundPaddingEnd()3011 public int getCompoundPaddingEnd() { 3012 resolveDrawables(); 3013 switch(getLayoutDirection()) { 3014 default: 3015 case LAYOUT_DIRECTION_LTR: 3016 return getCompoundPaddingRight(); 3017 case LAYOUT_DIRECTION_RTL: 3018 return getCompoundPaddingLeft(); 3019 } 3020 } 3021 3022 /** 3023 * Returns the extended top padding of the view, including both the 3024 * top Drawable if any and any extra space to keep more than maxLines 3025 * of text from showing. It is only valid to call this after measuring. 3026 */ getExtendedPaddingTop()3027 public int getExtendedPaddingTop() { 3028 if (mMaxMode != LINES) { 3029 return getCompoundPaddingTop(); 3030 } 3031 3032 if (mLayout == null) { 3033 assumeLayout(); 3034 } 3035 3036 if (mLayout.getLineCount() <= mMaximum) { 3037 return getCompoundPaddingTop(); 3038 } 3039 3040 int top = getCompoundPaddingTop(); 3041 int bottom = getCompoundPaddingBottom(); 3042 int viewht = getHeight() - top - bottom; 3043 int layoutht = mLayout.getLineTop(mMaximum); 3044 3045 if (layoutht >= viewht) { 3046 return top; 3047 } 3048 3049 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 3050 if (gravity == Gravity.TOP) { 3051 return top; 3052 } else if (gravity == Gravity.BOTTOM) { 3053 return top + viewht - layoutht; 3054 } else { // (gravity == Gravity.CENTER_VERTICAL) 3055 return top + (viewht - layoutht) / 2; 3056 } 3057 } 3058 3059 /** 3060 * Returns the extended bottom padding of the view, including both the 3061 * bottom Drawable if any and any extra space to keep more than maxLines 3062 * of text from showing. It is only valid to call this after measuring. 3063 */ getExtendedPaddingBottom()3064 public int getExtendedPaddingBottom() { 3065 if (mMaxMode != LINES) { 3066 return getCompoundPaddingBottom(); 3067 } 3068 3069 if (mLayout == null) { 3070 assumeLayout(); 3071 } 3072 3073 if (mLayout.getLineCount() <= mMaximum) { 3074 return getCompoundPaddingBottom(); 3075 } 3076 3077 int top = getCompoundPaddingTop(); 3078 int bottom = getCompoundPaddingBottom(); 3079 int viewht = getHeight() - top - bottom; 3080 int layoutht = mLayout.getLineTop(mMaximum); 3081 3082 if (layoutht >= viewht) { 3083 return bottom; 3084 } 3085 3086 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 3087 if (gravity == Gravity.TOP) { 3088 return bottom + viewht - layoutht; 3089 } else if (gravity == Gravity.BOTTOM) { 3090 return bottom; 3091 } else { // (gravity == Gravity.CENTER_VERTICAL) 3092 return bottom + (viewht - layoutht) / 2; 3093 } 3094 } 3095 3096 /** 3097 * Returns the total left padding of the view, including the left 3098 * Drawable if any. 3099 */ getTotalPaddingLeft()3100 public int getTotalPaddingLeft() { 3101 return getCompoundPaddingLeft(); 3102 } 3103 3104 /** 3105 * Returns the total right padding of the view, including the right 3106 * Drawable if any. 3107 */ getTotalPaddingRight()3108 public int getTotalPaddingRight() { 3109 return getCompoundPaddingRight(); 3110 } 3111 3112 /** 3113 * Returns the total start padding of the view, including the start 3114 * Drawable if any. 3115 */ getTotalPaddingStart()3116 public int getTotalPaddingStart() { 3117 return getCompoundPaddingStart(); 3118 } 3119 3120 /** 3121 * Returns the total end padding of the view, including the end 3122 * Drawable if any. 3123 */ getTotalPaddingEnd()3124 public int getTotalPaddingEnd() { 3125 return getCompoundPaddingEnd(); 3126 } 3127 3128 /** 3129 * Returns the total top padding of the view, including the top 3130 * Drawable if any, the extra space to keep more than maxLines 3131 * from showing, and the vertical offset for gravity, if any. 3132 */ getTotalPaddingTop()3133 public int getTotalPaddingTop() { 3134 return getExtendedPaddingTop() + getVerticalOffset(true); 3135 } 3136 3137 /** 3138 * Returns the total bottom padding of the view, including the bottom 3139 * Drawable if any, the extra space to keep more than maxLines 3140 * from showing, and the vertical offset for gravity, if any. 3141 */ getTotalPaddingBottom()3142 public int getTotalPaddingBottom() { 3143 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 3144 } 3145 3146 /** 3147 * Sets the Drawables (if any) to appear to the left of, above, to the 3148 * right of, and below the text. Use {@code null} if you do not want a 3149 * Drawable there. The Drawables must already have had 3150 * {@link Drawable#setBounds} called. 3151 * <p> 3152 * Calling this method will overwrite any Drawables previously set using 3153 * {@link #setCompoundDrawablesRelative} or related methods. 3154 * 3155 * @attr ref android.R.styleable#TextView_drawableLeft 3156 * @attr ref android.R.styleable#TextView_drawableTop 3157 * @attr ref android.R.styleable#TextView_drawableRight 3158 * @attr ref android.R.styleable#TextView_drawableBottom 3159 */ setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3160 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top, 3161 @Nullable Drawable right, @Nullable Drawable bottom) { 3162 Drawables dr = mDrawables; 3163 3164 // We're switching to absolute, discard relative. 3165 if (dr != null) { 3166 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 3167 dr.mDrawableStart = null; 3168 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); 3169 dr.mDrawableEnd = null; 3170 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3171 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3172 } 3173 3174 final boolean drawables = left != null || top != null || right != null || bottom != null; 3175 if (!drawables) { 3176 // Clearing drawables... can we free the data structure? 3177 if (dr != null) { 3178 if (!dr.hasMetadata()) { 3179 mDrawables = null; 3180 } else { 3181 // We need to retain the last set padding, so just clear 3182 // out all of the fields in the existing structure. 3183 for (int i = dr.mShowing.length - 1; i >= 0; i--) { 3184 if (dr.mShowing[i] != null) { 3185 dr.mShowing[i].setCallback(null); 3186 } 3187 dr.mShowing[i] = null; 3188 } 3189 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3190 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3191 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3192 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3193 } 3194 } 3195 } else { 3196 if (dr == null) { 3197 mDrawables = dr = new Drawables(getContext()); 3198 } 3199 3200 mDrawables.mOverride = false; 3201 3202 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) { 3203 dr.mShowing[Drawables.LEFT].setCallback(null); 3204 } 3205 dr.mShowing[Drawables.LEFT] = left; 3206 3207 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 3208 dr.mShowing[Drawables.TOP].setCallback(null); 3209 } 3210 dr.mShowing[Drawables.TOP] = top; 3211 3212 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) { 3213 dr.mShowing[Drawables.RIGHT].setCallback(null); 3214 } 3215 dr.mShowing[Drawables.RIGHT] = right; 3216 3217 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 3218 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3219 } 3220 dr.mShowing[Drawables.BOTTOM] = bottom; 3221 3222 final Rect compoundRect = dr.mCompoundRect; 3223 int[] state; 3224 3225 state = getDrawableState(); 3226 3227 if (left != null) { 3228 left.setState(state); 3229 left.copyBounds(compoundRect); 3230 left.setCallback(this); 3231 dr.mDrawableSizeLeft = compoundRect.width(); 3232 dr.mDrawableHeightLeft = compoundRect.height(); 3233 } else { 3234 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3235 } 3236 3237 if (right != null) { 3238 right.setState(state); 3239 right.copyBounds(compoundRect); 3240 right.setCallback(this); 3241 dr.mDrawableSizeRight = compoundRect.width(); 3242 dr.mDrawableHeightRight = compoundRect.height(); 3243 } else { 3244 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3245 } 3246 3247 if (top != null) { 3248 top.setState(state); 3249 top.copyBounds(compoundRect); 3250 top.setCallback(this); 3251 dr.mDrawableSizeTop = compoundRect.height(); 3252 dr.mDrawableWidthTop = compoundRect.width(); 3253 } else { 3254 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3255 } 3256 3257 if (bottom != null) { 3258 bottom.setState(state); 3259 bottom.copyBounds(compoundRect); 3260 bottom.setCallback(this); 3261 dr.mDrawableSizeBottom = compoundRect.height(); 3262 dr.mDrawableWidthBottom = compoundRect.width(); 3263 } else { 3264 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3265 } 3266 } 3267 3268 // Save initial left/right drawables 3269 if (dr != null) { 3270 dr.mDrawableLeftInitial = left; 3271 dr.mDrawableRightInitial = right; 3272 } 3273 3274 resetResolvedDrawables(); 3275 resolveDrawables(); 3276 applyCompoundDrawableTint(); 3277 invalidate(); 3278 requestLayout(); 3279 } 3280 3281 /** 3282 * Sets the Drawables (if any) to appear to the left of, above, to the 3283 * right of, and below the text. Use 0 if you do not want a Drawable there. 3284 * The Drawables' bounds will be set to their intrinsic bounds. 3285 * <p> 3286 * Calling this method will overwrite any Drawables previously set using 3287 * {@link #setCompoundDrawablesRelative} or related methods. 3288 * 3289 * @param left Resource identifier of the left Drawable. 3290 * @param top Resource identifier of the top Drawable. 3291 * @param right Resource identifier of the right Drawable. 3292 * @param bottom Resource identifier of the bottom Drawable. 3293 * 3294 * @attr ref android.R.styleable#TextView_drawableLeft 3295 * @attr ref android.R.styleable#TextView_drawableTop 3296 * @attr ref android.R.styleable#TextView_drawableRight 3297 * @attr ref android.R.styleable#TextView_drawableBottom 3298 */ 3299 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)3300 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left, 3301 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { 3302 final Context context = getContext(); 3303 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null, 3304 top != 0 ? context.getDrawable(top) : null, 3305 right != 0 ? context.getDrawable(right) : null, 3306 bottom != 0 ? context.getDrawable(bottom) : null); 3307 } 3308 3309 /** 3310 * Sets the Drawables (if any) to appear to the left of, above, to the 3311 * right of, and below the text. Use {@code null} if you do not want a 3312 * Drawable there. The Drawables' bounds will be set to their intrinsic 3313 * bounds. 3314 * <p> 3315 * Calling this method will overwrite any Drawables previously set using 3316 * {@link #setCompoundDrawablesRelative} or related methods. 3317 * 3318 * @attr ref android.R.styleable#TextView_drawableLeft 3319 * @attr ref android.R.styleable#TextView_drawableTop 3320 * @attr ref android.R.styleable#TextView_drawableRight 3321 * @attr ref android.R.styleable#TextView_drawableBottom 3322 */ 3323 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3324 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left, 3325 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { 3326 3327 if (left != null) { 3328 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 3329 } 3330 if (right != null) { 3331 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 3332 } 3333 if (top != null) { 3334 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3335 } 3336 if (bottom != null) { 3337 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3338 } 3339 setCompoundDrawables(left, top, right, bottom); 3340 } 3341 3342 /** 3343 * Sets the Drawables (if any) to appear to the start of, above, to the end 3344 * of, and below the text. Use {@code null} if you do not want a Drawable 3345 * there. The Drawables must already have had {@link Drawable#setBounds} 3346 * called. 3347 * <p> 3348 * Calling this method will overwrite any Drawables previously set using 3349 * {@link #setCompoundDrawables} or related methods. 3350 * 3351 * @attr ref android.R.styleable#TextView_drawableStart 3352 * @attr ref android.R.styleable#TextView_drawableTop 3353 * @attr ref android.R.styleable#TextView_drawableEnd 3354 * @attr ref android.R.styleable#TextView_drawableBottom 3355 */ 3356 @android.view.RemotableViewMethod setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3357 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top, 3358 @Nullable Drawable end, @Nullable Drawable bottom) { 3359 Drawables dr = mDrawables; 3360 3361 // We're switching to relative, discard absolute. 3362 if (dr != null) { 3363 if (dr.mShowing[Drawables.LEFT] != null) { 3364 dr.mShowing[Drawables.LEFT].setCallback(null); 3365 } 3366 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null; 3367 if (dr.mShowing[Drawables.RIGHT] != null) { 3368 dr.mShowing[Drawables.RIGHT].setCallback(null); 3369 } 3370 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null; 3371 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3372 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3373 } 3374 3375 final boolean drawables = start != null || top != null 3376 || end != null || bottom != null; 3377 3378 if (!drawables) { 3379 // Clearing drawables... can we free the data structure? 3380 if (dr != null) { 3381 if (!dr.hasMetadata()) { 3382 mDrawables = null; 3383 } else { 3384 // We need to retain the last set padding, so just clear 3385 // out all of the fields in the existing structure. 3386 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 3387 dr.mDrawableStart = null; 3388 if (dr.mShowing[Drawables.TOP] != null) { 3389 dr.mShowing[Drawables.TOP].setCallback(null); 3390 } 3391 dr.mShowing[Drawables.TOP] = null; 3392 if (dr.mDrawableEnd != null) { 3393 dr.mDrawableEnd.setCallback(null); 3394 } 3395 dr.mDrawableEnd = null; 3396 if (dr.mShowing[Drawables.BOTTOM] != null) { 3397 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3398 } 3399 dr.mShowing[Drawables.BOTTOM] = null; 3400 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3401 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3402 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3403 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3404 } 3405 } 3406 } else { 3407 if (dr == null) { 3408 mDrawables = dr = new Drawables(getContext()); 3409 } 3410 3411 mDrawables.mOverride = true; 3412 3413 if (dr.mDrawableStart != start && dr.mDrawableStart != null) { 3414 dr.mDrawableStart.setCallback(null); 3415 } 3416 dr.mDrawableStart = start; 3417 3418 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 3419 dr.mShowing[Drawables.TOP].setCallback(null); 3420 } 3421 dr.mShowing[Drawables.TOP] = top; 3422 3423 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) { 3424 dr.mDrawableEnd.setCallback(null); 3425 } 3426 dr.mDrawableEnd = end; 3427 3428 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 3429 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3430 } 3431 dr.mShowing[Drawables.BOTTOM] = bottom; 3432 3433 final Rect compoundRect = dr.mCompoundRect; 3434 int[] state; 3435 3436 state = getDrawableState(); 3437 3438 if (start != null) { 3439 start.setState(state); 3440 start.copyBounds(compoundRect); 3441 start.setCallback(this); 3442 dr.mDrawableSizeStart = compoundRect.width(); 3443 dr.mDrawableHeightStart = compoundRect.height(); 3444 } else { 3445 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3446 } 3447 3448 if (end != null) { 3449 end.setState(state); 3450 end.copyBounds(compoundRect); 3451 end.setCallback(this); 3452 dr.mDrawableSizeEnd = compoundRect.width(); 3453 dr.mDrawableHeightEnd = compoundRect.height(); 3454 } else { 3455 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3456 } 3457 3458 if (top != null) { 3459 top.setState(state); 3460 top.copyBounds(compoundRect); 3461 top.setCallback(this); 3462 dr.mDrawableSizeTop = compoundRect.height(); 3463 dr.mDrawableWidthTop = compoundRect.width(); 3464 } else { 3465 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3466 } 3467 3468 if (bottom != null) { 3469 bottom.setState(state); 3470 bottom.copyBounds(compoundRect); 3471 bottom.setCallback(this); 3472 dr.mDrawableSizeBottom = compoundRect.height(); 3473 dr.mDrawableWidthBottom = compoundRect.width(); 3474 } else { 3475 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3476 } 3477 } 3478 3479 resetResolvedDrawables(); 3480 resolveDrawables(); 3481 invalidate(); 3482 requestLayout(); 3483 } 3484 3485 /** 3486 * Sets the Drawables (if any) to appear to the start of, above, to the end 3487 * of, and below the text. Use 0 if you do not want a Drawable there. The 3488 * Drawables' bounds will be set to their intrinsic bounds. 3489 * <p> 3490 * Calling this method will overwrite any Drawables previously set using 3491 * {@link #setCompoundDrawables} or related methods. 3492 * 3493 * @param start Resource identifier of the start Drawable. 3494 * @param top Resource identifier of the top Drawable. 3495 * @param end Resource identifier of the end Drawable. 3496 * @param bottom Resource identifier of the bottom Drawable. 3497 * 3498 * @attr ref android.R.styleable#TextView_drawableStart 3499 * @attr ref android.R.styleable#TextView_drawableTop 3500 * @attr ref android.R.styleable#TextView_drawableEnd 3501 * @attr ref android.R.styleable#TextView_drawableBottom 3502 */ 3503 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3504 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start, 3505 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) { 3506 final Context context = getContext(); 3507 setCompoundDrawablesRelativeWithIntrinsicBounds( 3508 start != 0 ? context.getDrawable(start) : null, 3509 top != 0 ? context.getDrawable(top) : null, 3510 end != 0 ? context.getDrawable(end) : null, 3511 bottom != 0 ? context.getDrawable(bottom) : null); 3512 } 3513 3514 /** 3515 * Sets the Drawables (if any) to appear to the start of, above, to the end 3516 * of, and below the text. Use {@code null} if you do not want a Drawable 3517 * there. The Drawables' bounds will be set to their intrinsic bounds. 3518 * <p> 3519 * Calling this method will overwrite any Drawables previously set using 3520 * {@link #setCompoundDrawables} or related methods. 3521 * 3522 * @attr ref android.R.styleable#TextView_drawableStart 3523 * @attr ref android.R.styleable#TextView_drawableTop 3524 * @attr ref android.R.styleable#TextView_drawableEnd 3525 * @attr ref android.R.styleable#TextView_drawableBottom 3526 */ 3527 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3528 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start, 3529 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { 3530 3531 if (start != null) { 3532 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 3533 } 3534 if (end != null) { 3535 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 3536 } 3537 if (top != null) { 3538 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3539 } 3540 if (bottom != null) { 3541 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3542 } 3543 setCompoundDrawablesRelative(start, top, end, bottom); 3544 } 3545 3546 /** 3547 * Returns drawables for the left, top, right, and bottom borders. 3548 * 3549 * @attr ref android.R.styleable#TextView_drawableLeft 3550 * @attr ref android.R.styleable#TextView_drawableTop 3551 * @attr ref android.R.styleable#TextView_drawableRight 3552 * @attr ref android.R.styleable#TextView_drawableBottom 3553 */ 3554 @NonNull getCompoundDrawables()3555 public Drawable[] getCompoundDrawables() { 3556 final Drawables dr = mDrawables; 3557 if (dr != null) { 3558 return dr.mShowing.clone(); 3559 } else { 3560 return new Drawable[] { null, null, null, null }; 3561 } 3562 } 3563 3564 /** 3565 * Returns drawables for the start, top, end, and bottom borders. 3566 * 3567 * @attr ref android.R.styleable#TextView_drawableStart 3568 * @attr ref android.R.styleable#TextView_drawableTop 3569 * @attr ref android.R.styleable#TextView_drawableEnd 3570 * @attr ref android.R.styleable#TextView_drawableBottom 3571 */ 3572 @NonNull getCompoundDrawablesRelative()3573 public Drawable[] getCompoundDrawablesRelative() { 3574 final Drawables dr = mDrawables; 3575 if (dr != null) { 3576 return new Drawable[] { 3577 dr.mDrawableStart, dr.mShowing[Drawables.TOP], 3578 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM] 3579 }; 3580 } else { 3581 return new Drawable[] { null, null, null, null }; 3582 } 3583 } 3584 3585 /** 3586 * Sets the size of the padding between the compound drawables and 3587 * the text. 3588 * 3589 * @attr ref android.R.styleable#TextView_drawablePadding 3590 */ 3591 @android.view.RemotableViewMethod setCompoundDrawablePadding(int pad)3592 public void setCompoundDrawablePadding(int pad) { 3593 Drawables dr = mDrawables; 3594 if (pad == 0) { 3595 if (dr != null) { 3596 dr.mDrawablePadding = pad; 3597 } 3598 } else { 3599 if (dr == null) { 3600 mDrawables = dr = new Drawables(getContext()); 3601 } 3602 dr.mDrawablePadding = pad; 3603 } 3604 3605 invalidate(); 3606 requestLayout(); 3607 } 3608 3609 /** 3610 * Returns the padding between the compound drawables and the text. 3611 * 3612 * @attr ref android.R.styleable#TextView_drawablePadding 3613 */ 3614 @InspectableProperty(name = "drawablePadding") getCompoundDrawablePadding()3615 public int getCompoundDrawablePadding() { 3616 final Drawables dr = mDrawables; 3617 return dr != null ? dr.mDrawablePadding : 0; 3618 } 3619 3620 /** 3621 * Applies a tint to the compound drawables. Does not modify the 3622 * current tint mode, which is {@link BlendMode#SRC_IN} by default. 3623 * <p> 3624 * Subsequent calls to 3625 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} 3626 * and related methods will automatically mutate the drawables and apply 3627 * the specified tint and tint mode using 3628 * {@link Drawable#setTintList(ColorStateList)}. 3629 * 3630 * @param tint the tint to apply, may be {@code null} to clear tint 3631 * 3632 * @attr ref android.R.styleable#TextView_drawableTint 3633 * @see #getCompoundDrawableTintList() 3634 * @see Drawable#setTintList(ColorStateList) 3635 */ setCompoundDrawableTintList(@ullable ColorStateList tint)3636 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) { 3637 if (mDrawables == null) { 3638 mDrawables = new Drawables(getContext()); 3639 } 3640 mDrawables.mTintList = tint; 3641 mDrawables.mHasTint = true; 3642 3643 applyCompoundDrawableTint(); 3644 } 3645 3646 /** 3647 * @return the tint applied to the compound drawables 3648 * @attr ref android.R.styleable#TextView_drawableTint 3649 * @see #setCompoundDrawableTintList(ColorStateList) 3650 */ 3651 @InspectableProperty(name = "drawableTint") getCompoundDrawableTintList()3652 public ColorStateList getCompoundDrawableTintList() { 3653 return mDrawables != null ? mDrawables.mTintList : 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 tintMode 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#setTintMode(PorterDuff.Mode) 3666 */ setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3667 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) { 3668 setCompoundDrawableTintBlendMode(tintMode != null 3669 ? BlendMode.fromValue(tintMode.nativeInt) : null); 3670 } 3671 3672 /** 3673 * Specifies the blending mode used to apply the tint specified by 3674 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3675 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3676 * 3677 * @param blendMode the blending mode used to apply the tint, may be 3678 * {@code null} to clear tint 3679 * @attr ref android.R.styleable#TextView_drawableTintMode 3680 * @see #setCompoundDrawableTintList(ColorStateList) 3681 * @see Drawable#setTintBlendMode(BlendMode) 3682 */ setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3683 public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) { 3684 if (mDrawables == null) { 3685 mDrawables = new Drawables(getContext()); 3686 } 3687 mDrawables.mBlendMode = blendMode; 3688 mDrawables.mHasTintMode = true; 3689 3690 applyCompoundDrawableTint(); 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 #setCompoundDrawableTintMode(PorterDuff.Mode) 3701 * 3702 */ 3703 @InspectableProperty(name = "drawableTintMode") getCompoundDrawableTintMode()3704 public PorterDuff.Mode getCompoundDrawableTintMode() { 3705 BlendMode mode = getCompoundDrawableTintBlendMode(); 3706 return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; 3707 } 3708 3709 /** 3710 * Returns the blending mode used to apply the tint to the compound 3711 * drawables, if specified. 3712 * 3713 * @return the blending mode used to apply the tint to the compound 3714 * drawables 3715 * @attr ref android.R.styleable#TextView_drawableTintMode 3716 * @see #setCompoundDrawableTintBlendMode(BlendMode) 3717 */ 3718 @InspectableProperty(name = "drawableBlendMode", 3719 attributeId = com.android.internal.R.styleable.TextView_drawableTintMode) getCompoundDrawableTintBlendMode()3720 public @Nullable BlendMode getCompoundDrawableTintBlendMode() { 3721 return mDrawables != null ? mDrawables.mBlendMode : null; 3722 } 3723 applyCompoundDrawableTint()3724 private void applyCompoundDrawableTint() { 3725 if (mDrawables == null) { 3726 return; 3727 } 3728 3729 if (mDrawables.mHasTint || mDrawables.mHasTintMode) { 3730 final ColorStateList tintList = mDrawables.mTintList; 3731 final BlendMode blendMode = mDrawables.mBlendMode; 3732 final boolean hasTint = mDrawables.mHasTint; 3733 final boolean hasTintMode = mDrawables.mHasTintMode; 3734 final int[] state = getDrawableState(); 3735 3736 for (Drawable dr : mDrawables.mShowing) { 3737 if (dr == null) { 3738 continue; 3739 } 3740 3741 if (dr == mDrawables.mDrawableError) { 3742 // From a developer's perspective, the error drawable isn't 3743 // a compound drawable. Don't apply the generic compound 3744 // drawable tint to it. 3745 continue; 3746 } 3747 3748 dr.mutate(); 3749 3750 if (hasTint) { 3751 dr.setTintList(tintList); 3752 } 3753 3754 if (hasTintMode) { 3755 dr.setTintBlendMode(blendMode); 3756 } 3757 3758 // The drawable (or one of its children) may not have been 3759 // stateful before applying the tint, so let's try again. 3760 if (dr.isStateful()) { 3761 dr.setState(state); 3762 } 3763 } 3764 } 3765 } 3766 3767 /** 3768 * @inheritDoc 3769 * 3770 * @see #setFirstBaselineToTopHeight(int) 3771 * @see #setLastBaselineToBottomHeight(int) 3772 */ 3773 @Override setPadding(int left, int top, int right, int bottom)3774 public void setPadding(int left, int top, int right, int bottom) { 3775 if (left != mPaddingLeft 3776 || right != mPaddingRight 3777 || top != mPaddingTop 3778 || bottom != mPaddingBottom) { 3779 nullLayouts(); 3780 } 3781 3782 // the super call will requestLayout() 3783 super.setPadding(left, top, right, bottom); 3784 invalidate(); 3785 } 3786 3787 /** 3788 * @inheritDoc 3789 * 3790 * @see #setFirstBaselineToTopHeight(int) 3791 * @see #setLastBaselineToBottomHeight(int) 3792 */ 3793 @Override setPaddingRelative(int start, int top, int end, int bottom)3794 public void setPaddingRelative(int start, int top, int end, int bottom) { 3795 if (start != getPaddingStart() 3796 || end != getPaddingEnd() 3797 || top != mPaddingTop 3798 || bottom != mPaddingBottom) { 3799 nullLayouts(); 3800 } 3801 3802 // the super call will requestLayout() 3803 super.setPaddingRelative(start, top, end, bottom); 3804 invalidate(); 3805 } 3806 3807 /** 3808 * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is 3809 * the distance between the top of the TextView and first line's baseline. 3810 * <p> 3811 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3812 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3813 * 3814 * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was 3815 * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated. 3816 * Moreover since this function sets the top padding, if the height of the TextView is less than 3817 * the sum of top padding, line height and bottom padding, top of the line will be pushed 3818 * down and bottom will be clipped. 3819 * 3820 * @param firstBaselineToTopHeight distance between first baseline to top of the container 3821 * in pixels 3822 * 3823 * @see #getFirstBaselineToTopHeight() 3824 * @see #setLastBaselineToBottomHeight(int) 3825 * @see #setPadding(int, int, int, int) 3826 * @see #setPaddingRelative(int, int, int, int) 3827 * 3828 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3829 */ setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3830 public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) { 3831 Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight); 3832 3833 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3834 final int fontMetricsTop; 3835 if (getIncludeFontPadding()) { 3836 fontMetricsTop = fontMetrics.top; 3837 } else { 3838 fontMetricsTop = fontMetrics.ascent; 3839 } 3840 3841 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3842 // in settings). At the moment, we don't. 3843 3844 if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) { 3845 final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop); 3846 setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom()); 3847 } 3848 } 3849 3850 /** 3851 * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is 3852 * the distance between the bottom of the TextView and the last line's baseline. 3853 * <p> 3854 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3855 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3856 * 3857 * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was 3858 * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated. 3859 * Moreover since this function sets the bottom padding, if the height of the TextView is less 3860 * than the sum of top padding, line height and bottom padding, bottom of the text will be 3861 * clipped. 3862 * 3863 * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container 3864 * in pixels 3865 * 3866 * @see #getLastBaselineToBottomHeight() 3867 * @see #setFirstBaselineToTopHeight(int) 3868 * @see #setPadding(int, int, int, int) 3869 * @see #setPaddingRelative(int, int, int, int) 3870 * 3871 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3872 */ setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3873 public void setLastBaselineToBottomHeight( 3874 @Px @IntRange(from = 0) int lastBaselineToBottomHeight) { 3875 Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight); 3876 3877 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3878 final int fontMetricsBottom; 3879 if (getIncludeFontPadding()) { 3880 fontMetricsBottom = fontMetrics.bottom; 3881 } else { 3882 fontMetricsBottom = fontMetrics.descent; 3883 } 3884 3885 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3886 // in settings). At the moment, we don't. 3887 3888 if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) { 3889 final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom; 3890 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom); 3891 } 3892 } 3893 3894 /** 3895 * Returns the distance between the first text baseline and the top of this TextView. 3896 * 3897 * @see #setFirstBaselineToTopHeight(int) 3898 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3899 */ 3900 @InspectableProperty getFirstBaselineToTopHeight()3901 public int getFirstBaselineToTopHeight() { 3902 return getPaddingTop() - getPaint().getFontMetricsInt().top; 3903 } 3904 3905 /** 3906 * Returns the distance between the last text baseline and the bottom of this TextView. 3907 * 3908 * @see #setLastBaselineToBottomHeight(int) 3909 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3910 */ 3911 @InspectableProperty getLastBaselineToBottomHeight()3912 public int getLastBaselineToBottomHeight() { 3913 return getPaddingBottom() + getPaint().getFontMetricsInt().bottom; 3914 } 3915 3916 /** 3917 * Gets the autolink mask of the text. 3918 * 3919 * See {@link Linkify#ALL} and peers for possible values. 3920 * 3921 * @attr ref android.R.styleable#TextView_autoLink 3922 */ 3923 @InspectableProperty(name = "autoLink", flagMapping = { 3924 @FlagEntry(name = "web", target = Linkify.WEB_URLS), 3925 @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES), 3926 @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS), 3927 @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES) 3928 }) getAutoLinkMask()3929 public final int getAutoLinkMask() { 3930 return mAutoLinkMask; 3931 } 3932 3933 /** 3934 * Sets the Drawable corresponding to the selection handle used for 3935 * positioning the cursor within text. The Drawable defaults to the value 3936 * of the textSelectHandle attribute. 3937 * Note that any change applied to the handle Drawable will not be visible 3938 * until the handle is hidden and then drawn again. 3939 * 3940 * @see #setTextSelectHandle(int) 3941 * @attr ref android.R.styleable#TextView_textSelectHandle 3942 */ 3943 @android.view.RemotableViewMethod setTextSelectHandle(@onNull Drawable textSelectHandle)3944 public void setTextSelectHandle(@NonNull Drawable textSelectHandle) { 3945 Preconditions.checkNotNull(textSelectHandle, 3946 "The text select handle should not be null."); 3947 mTextSelectHandle = textSelectHandle; 3948 mTextSelectHandleRes = 0; 3949 if (mEditor != null) { 3950 mEditor.loadHandleDrawables(true /* overwrite */); 3951 } 3952 } 3953 3954 /** 3955 * Sets the Drawable corresponding to the selection handle used for 3956 * positioning the cursor within text. The Drawable defaults to the value 3957 * of the textSelectHandle attribute. 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 * @see #setTextSelectHandle(Drawable) 3962 * @attr ref android.R.styleable#TextView_textSelectHandle 3963 */ 3964 @android.view.RemotableViewMethod setTextSelectHandle(@rawableRes int textSelectHandle)3965 public void setTextSelectHandle(@DrawableRes int textSelectHandle) { 3966 Preconditions.checkArgument(textSelectHandle != 0, 3967 "The text select handle should be a valid drawable resource id."); 3968 setTextSelectHandle(mContext.getDrawable(textSelectHandle)); 3969 } 3970 3971 /** 3972 * Returns the Drawable corresponding to the selection handle used 3973 * for positioning the cursor within text. 3974 * Note that any change applied to the handle Drawable will not be visible 3975 * until the handle is hidden and then drawn again. 3976 * 3977 * @return the text select handle drawable 3978 * 3979 * @see #setTextSelectHandle(Drawable) 3980 * @see #setTextSelectHandle(int) 3981 * @attr ref android.R.styleable#TextView_textSelectHandle 3982 */ getTextSelectHandle()3983 @Nullable public Drawable getTextSelectHandle() { 3984 if (mTextSelectHandle == null && mTextSelectHandleRes != 0) { 3985 mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes); 3986 } 3987 return mTextSelectHandle; 3988 } 3989 3990 /** 3991 * Sets the Drawable corresponding to the left handle used 3992 * for selecting text. The Drawable defaults to the value of the 3993 * textSelectHandleLeft attribute. 3994 * Note that any change applied to the handle Drawable will not be visible 3995 * until the handle is hidden and then drawn again. 3996 * 3997 * @see #setTextSelectHandleLeft(int) 3998 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3999 */ 4000 @android.view.RemotableViewMethod setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)4001 public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) { 4002 Preconditions.checkNotNull(textSelectHandleLeft, 4003 "The left text select handle should not be null."); 4004 mTextSelectHandleLeft = textSelectHandleLeft; 4005 mTextSelectHandleLeftRes = 0; 4006 if (mEditor != null) { 4007 mEditor.loadHandleDrawables(true /* overwrite */); 4008 } 4009 } 4010 4011 /** 4012 * Sets the Drawable corresponding to the left handle used 4013 * for selecting text. The Drawable defaults to the value of the 4014 * textSelectHandleLeft attribute. 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 * @see #setTextSelectHandleLeft(Drawable) 4019 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 4020 */ 4021 @android.view.RemotableViewMethod setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)4022 public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) { 4023 Preconditions.checkArgument(textSelectHandleLeft != 0, 4024 "The text select left handle should be a valid drawable resource id."); 4025 setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft)); 4026 } 4027 4028 /** 4029 * Returns the Drawable corresponding to the left handle used 4030 * for selecting text. 4031 * Note that any change applied to the handle Drawable will not be visible 4032 * until the handle is hidden and then drawn again. 4033 * 4034 * @return the left text selection handle drawable 4035 * 4036 * @see #setTextSelectHandleLeft(Drawable) 4037 * @see #setTextSelectHandleLeft(int) 4038 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 4039 */ getTextSelectHandleLeft()4040 @Nullable public Drawable getTextSelectHandleLeft() { 4041 if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) { 4042 mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes); 4043 } 4044 return mTextSelectHandleLeft; 4045 } 4046 4047 /** 4048 * Sets the Drawable corresponding to the right handle used 4049 * for selecting text. The Drawable defaults to the value of the 4050 * textSelectHandleRight attribute. 4051 * Note that any change applied to the handle Drawable will not be visible 4052 * until the handle is hidden and then drawn again. 4053 * 4054 * @see #setTextSelectHandleRight(int) 4055 * @attr ref android.R.styleable#TextView_textSelectHandleRight 4056 */ 4057 @android.view.RemotableViewMethod setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)4058 public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) { 4059 Preconditions.checkNotNull(textSelectHandleRight, 4060 "The right text select handle should not be null."); 4061 mTextSelectHandleRight = textSelectHandleRight; 4062 mTextSelectHandleRightRes = 0; 4063 if (mEditor != null) { 4064 mEditor.loadHandleDrawables(true /* overwrite */); 4065 } 4066 } 4067 4068 /** 4069 * Sets the Drawable corresponding to the right handle used 4070 * for selecting text. The Drawable defaults to the value of the 4071 * textSelectHandleRight attribute. 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 * @see #setTextSelectHandleRight(Drawable) 4076 * @attr ref android.R.styleable#TextView_textSelectHandleRight 4077 */ 4078 @android.view.RemotableViewMethod setTextSelectHandleRight(@rawableRes int textSelectHandleRight)4079 public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) { 4080 Preconditions.checkArgument(textSelectHandleRight != 0, 4081 "The text select right handle should be a valid drawable resource id."); 4082 setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight)); 4083 } 4084 4085 /** 4086 * Returns the Drawable corresponding to the right handle used 4087 * for selecting text. 4088 * Note that any change applied to the handle Drawable will not be visible 4089 * until the handle is hidden and then drawn again. 4090 * 4091 * @return the right text selection handle drawable 4092 * 4093 * @see #setTextSelectHandleRight(Drawable) 4094 * @see #setTextSelectHandleRight(int) 4095 * @attr ref android.R.styleable#TextView_textSelectHandleRight 4096 */ getTextSelectHandleRight()4097 @Nullable public Drawable getTextSelectHandleRight() { 4098 if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) { 4099 mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes); 4100 } 4101 return mTextSelectHandleRight; 4102 } 4103 4104 /** 4105 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 4106 * value of the textCursorDrawable attribute. 4107 * Note that any change applied to the cursor Drawable will not be visible 4108 * until the cursor is hidden and then drawn again. 4109 * 4110 * @see #setTextCursorDrawable(int) 4111 * @attr ref android.R.styleable#TextView_textCursorDrawable 4112 */ setTextCursorDrawable(@ullable Drawable textCursorDrawable)4113 public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) { 4114 mCursorDrawable = textCursorDrawable; 4115 mCursorDrawableRes = 0; 4116 if (mEditor != null) { 4117 mEditor.loadCursorDrawable(); 4118 } 4119 } 4120 4121 /** 4122 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 4123 * value of the textCursorDrawable attribute. 4124 * Note that any change applied to the cursor Drawable will not be visible 4125 * until the cursor is hidden and then drawn again. 4126 * 4127 * @see #setTextCursorDrawable(Drawable) 4128 * @attr ref android.R.styleable#TextView_textCursorDrawable 4129 */ setTextCursorDrawable(@rawableRes int textCursorDrawable)4130 public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) { 4131 setTextCursorDrawable( 4132 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null); 4133 } 4134 4135 /** 4136 * Returns the Drawable corresponding to the text cursor. 4137 * Note that any change applied to the cursor Drawable will not be visible 4138 * until the cursor is hidden and then drawn again. 4139 * 4140 * @return the text cursor drawable 4141 * 4142 * @see #setTextCursorDrawable(Drawable) 4143 * @see #setTextCursorDrawable(int) 4144 * @attr ref android.R.styleable#TextView_textCursorDrawable 4145 */ getTextCursorDrawable()4146 @Nullable public Drawable getTextCursorDrawable() { 4147 if (mCursorDrawable == null && mCursorDrawableRes != 0) { 4148 mCursorDrawable = mContext.getDrawable(mCursorDrawableRes); 4149 } 4150 return mCursorDrawable; 4151 } 4152 4153 /** 4154 * Sets the text appearance from the specified style resource. 4155 * <p> 4156 * Use a framework-defined {@code TextAppearance} style like 4157 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1} 4158 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the 4159 * set of attributes that can be used in a custom style. 4160 * 4161 * @param resId the resource identifier of the style to apply 4162 * @attr ref android.R.styleable#TextView_textAppearance 4163 */ 4164 @SuppressWarnings("deprecation") setTextAppearance(@tyleRes int resId)4165 public void setTextAppearance(@StyleRes int resId) { 4166 setTextAppearance(mContext, resId); 4167 } 4168 4169 /** 4170 * Sets the text color, size, style, hint color, and highlight color 4171 * from the specified TextAppearance resource. 4172 * 4173 * @deprecated Use {@link #setTextAppearance(int)} instead. 4174 */ 4175 @Deprecated setTextAppearance(Context context, @StyleRes int resId)4176 public void setTextAppearance(Context context, @StyleRes int resId) { 4177 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance); 4178 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 4179 readTextAppearance(context, ta, attributes, false /* styleArray */); 4180 ta.recycle(); 4181 applyTextAppearance(attributes); 4182 } 4183 4184 /** 4185 * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code 4186 * that reads these attributes in the constructor and in {@link #setTextAppearance}. 4187 */ 4188 private static class TextAppearanceAttributes { 4189 int mTextColorHighlight = 0; 4190 int mSearchResultHighlightColor = 0; 4191 int mFocusedSearchResultHighlightColor = 0; 4192 ColorStateList mTextColor = null; 4193 ColorStateList mTextColorHint = null; 4194 ColorStateList mTextColorLink = null; 4195 int mTextSize = -1; 4196 int mTextSizeUnit = -1; 4197 LocaleList mTextLocales = null; 4198 String mFontFamily = null; 4199 Typeface mFontTypeface = null; 4200 boolean mFontFamilyExplicit = false; 4201 int mTypefaceIndex = -1; 4202 int mTextStyle = 0; 4203 int mFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED; 4204 boolean mAllCaps = false; 4205 int mShadowColor = 0; 4206 float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0; 4207 boolean mHasElegant = false; 4208 boolean mElegant = false; 4209 boolean mHasFallbackLineSpacing = false; 4210 boolean mFallbackLineSpacing = false; 4211 boolean mHasLetterSpacing = false; 4212 float mLetterSpacing = 0; 4213 String mFontFeatureSettings = null; 4214 String mFontVariationSettings = null; 4215 boolean mHasLineBreakStyle = false; 4216 boolean mHasLineBreakWordStyle = false; 4217 int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; 4218 int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; 4219 4220 @Override toString()4221 public String toString() { 4222 return "TextAppearanceAttributes {\n" 4223 + " mTextColorHighlight:" + mTextColorHighlight + "\n" 4224 + " mSearchResultHighlightColor: " + mSearchResultHighlightColor + "\n" 4225 + " mFocusedSearchResultHighlightColor: " 4226 + mFocusedSearchResultHighlightColor + "\n" 4227 + " mTextColor:" + mTextColor + "\n" 4228 + " mTextColorHint:" + mTextColorHint + "\n" 4229 + " mTextColorLink:" + mTextColorLink + "\n" 4230 + " mTextSize:" + mTextSize + "\n" 4231 + " mTextSizeUnit:" + mTextSizeUnit + "\n" 4232 + " mTextLocales:" + mTextLocales + "\n" 4233 + " mFontFamily:" + mFontFamily + "\n" 4234 + " mFontTypeface:" + mFontTypeface + "\n" 4235 + " mFontFamilyExplicit:" + mFontFamilyExplicit + "\n" 4236 + " mTypefaceIndex:" + mTypefaceIndex + "\n" 4237 + " mTextStyle:" + mTextStyle + "\n" 4238 + " mFontWeight:" + mFontWeight + "\n" 4239 + " mAllCaps:" + mAllCaps + "\n" 4240 + " mShadowColor:" + mShadowColor + "\n" 4241 + " mShadowDx:" + mShadowDx + "\n" 4242 + " mShadowDy:" + mShadowDy + "\n" 4243 + " mShadowRadius:" + mShadowRadius + "\n" 4244 + " mHasElegant:" + mHasElegant + "\n" 4245 + " mElegant:" + mElegant + "\n" 4246 + " mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n" 4247 + " mFallbackLineSpacing:" + mFallbackLineSpacing + "\n" 4248 + " mHasLetterSpacing:" + mHasLetterSpacing + "\n" 4249 + " mLetterSpacing:" + mLetterSpacing + "\n" 4250 + " mFontFeatureSettings:" + mFontFeatureSettings + "\n" 4251 + " mFontVariationSettings:" + mFontVariationSettings + "\n" 4252 + " mHasLineBreakStyle:" + mHasLineBreakStyle + "\n" 4253 + " mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n" 4254 + " mLineBreakStyle:" + mLineBreakStyle + "\n" 4255 + " mLineBreakWordStyle:" + mLineBreakWordStyle + "\n" 4256 + "}"; 4257 } 4258 } 4259 4260 // Maps styleable attributes that exist both in TextView style and TextAppearance. 4261 private static final SparseIntArray sAppearanceValues = new SparseIntArray(); 4262 static { sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)4263 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, 4264 com.android.internal.R.styleable.TextAppearance_textColorHighlight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor, com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor)4265 sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor, 4266 com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor); sAppearanceValues.put( com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor, com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor)4267 sAppearanceValues.put( 4268 com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor, 4269 com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)4270 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, 4271 com.android.internal.R.styleable.TextAppearance_textColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)4272 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, 4273 com.android.internal.R.styleable.TextAppearance_textColorHint); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)4274 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, 4275 com.android.internal.R.styleable.TextAppearance_textColorLink); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)4276 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, 4277 com.android.internal.R.styleable.TextAppearance_textSize); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)4278 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, 4279 com.android.internal.R.styleable.TextAppearance_textLocale); sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)4280 sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, 4281 com.android.internal.R.styleable.TextAppearance_typeface); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)4282 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, 4283 com.android.internal.R.styleable.TextAppearance_fontFamily); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)4284 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, 4285 com.android.internal.R.styleable.TextAppearance_textStyle); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)4286 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, 4287 com.android.internal.R.styleable.TextAppearance_textFontWeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)4288 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, 4289 com.android.internal.R.styleable.TextAppearance_textAllCaps); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)4290 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, 4291 com.android.internal.R.styleable.TextAppearance_shadowColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)4292 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, 4293 com.android.internal.R.styleable.TextAppearance_shadowDx); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)4294 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, 4295 com.android.internal.R.styleable.TextAppearance_shadowDy); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)4296 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, 4297 com.android.internal.R.styleable.TextAppearance_shadowRadius); sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)4298 sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, 4299 com.android.internal.R.styleable.TextAppearance_elegantTextHeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)4300 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, 4301 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)4302 sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, 4303 com.android.internal.R.styleable.TextAppearance_letterSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)4304 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, 4305 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)4306 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, 4307 com.android.internal.R.styleable.TextAppearance_fontVariationSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, com.android.internal.R.styleable.TextAppearance_lineBreakStyle)4308 sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, 4309 com.android.internal.R.styleable.TextAppearance_lineBreakStyle); sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle, com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle)4310 sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle, 4311 com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle); 4312 } 4313 4314 /** 4315 * Read the Text Appearance attributes from a given TypedArray and set its values to the given 4316 * set. If the TypedArray contains a value that was already set in the given attributes, that 4317 * will be overridden. 4318 * 4319 * @param context The Context to be used 4320 * @param appearance The TypedArray to read properties from 4321 * @param attributes the TextAppearanceAttributes to fill in 4322 * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines 4323 * what attribute indexes will be used to read the properties. 4324 */ readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)4325 private void readTextAppearance(Context context, TypedArray appearance, 4326 TextAppearanceAttributes attributes, boolean styleArray) { 4327 final int n = appearance.getIndexCount(); 4328 for (int i = 0; i < n; i++) { 4329 final int attr = appearance.getIndex(i); 4330 int index = attr; 4331 // Translate style array index ids to TextAppearance ids. 4332 if (styleArray) { 4333 index = sAppearanceValues.get(attr, -1); 4334 if (index == -1) { 4335 // This value is not part of a Text Appearance and should be ignored. 4336 continue; 4337 } 4338 } 4339 switch (index) { 4340 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 4341 attributes.mTextColorHighlight = 4342 appearance.getColor(attr, attributes.mTextColorHighlight); 4343 break; 4344 case com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor: 4345 attributes.mSearchResultHighlightColor = 4346 appearance.getColor(attr, attributes.mSearchResultHighlightColor); 4347 break; 4348 case com.android.internal.R.styleable 4349 .TextAppearance_focusedSearchResultHighlightColor: 4350 attributes.mFocusedSearchResultHighlightColor = 4351 appearance.getColor(attr, 4352 attributes.mFocusedSearchResultHighlightColor); 4353 break; 4354 case com.android.internal.R.styleable.TextAppearance_textColor: 4355 attributes.mTextColor = appearance.getColorStateList(attr); 4356 break; 4357 case com.android.internal.R.styleable.TextAppearance_textColorHint: 4358 attributes.mTextColorHint = appearance.getColorStateList(attr); 4359 break; 4360 case com.android.internal.R.styleable.TextAppearance_textColorLink: 4361 attributes.mTextColorLink = appearance.getColorStateList(attr); 4362 break; 4363 case com.android.internal.R.styleable.TextAppearance_textSize: 4364 attributes.mTextSize = 4365 appearance.getDimensionPixelSize(attr, attributes.mTextSize); 4366 attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit(); 4367 break; 4368 case com.android.internal.R.styleable.TextAppearance_textLocale: 4369 final String localeString = appearance.getString(attr); 4370 if (localeString != null) { 4371 final LocaleList localeList = LocaleList.forLanguageTags(localeString); 4372 if (!localeList.isEmpty()) { 4373 attributes.mTextLocales = localeList; 4374 } 4375 } 4376 break; 4377 case com.android.internal.R.styleable.TextAppearance_typeface: 4378 attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex); 4379 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4380 attributes.mFontFamily = null; 4381 } 4382 break; 4383 case com.android.internal.R.styleable.TextAppearance_fontFamily: 4384 if (!context.isRestricted() && context.canLoadUnsafeResources()) { 4385 try { 4386 attributes.mFontTypeface = appearance.getFont(attr); 4387 } catch (UnsupportedOperationException | Resources.NotFoundException e) { 4388 // Expected if it is not a font resource. 4389 } 4390 } 4391 if (attributes.mFontTypeface == null) { 4392 attributes.mFontFamily = appearance.getString(attr); 4393 } 4394 attributes.mFontFamilyExplicit = true; 4395 break; 4396 case com.android.internal.R.styleable.TextAppearance_textStyle: 4397 attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle); 4398 break; 4399 case com.android.internal.R.styleable.TextAppearance_textFontWeight: 4400 attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight); 4401 break; 4402 case com.android.internal.R.styleable.TextAppearance_textAllCaps: 4403 attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps); 4404 break; 4405 case com.android.internal.R.styleable.TextAppearance_shadowColor: 4406 attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor); 4407 break; 4408 case com.android.internal.R.styleable.TextAppearance_shadowDx: 4409 attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx); 4410 break; 4411 case com.android.internal.R.styleable.TextAppearance_shadowDy: 4412 attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy); 4413 break; 4414 case com.android.internal.R.styleable.TextAppearance_shadowRadius: 4415 attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius); 4416 break; 4417 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight: 4418 attributes.mHasElegant = true; 4419 attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant); 4420 break; 4421 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing: 4422 attributes.mHasFallbackLineSpacing = true; 4423 attributes.mFallbackLineSpacing = appearance.getBoolean(attr, 4424 attributes.mFallbackLineSpacing); 4425 break; 4426 case com.android.internal.R.styleable.TextAppearance_letterSpacing: 4427 attributes.mHasLetterSpacing = true; 4428 attributes.mLetterSpacing = 4429 appearance.getFloat(attr, attributes.mLetterSpacing); 4430 break; 4431 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings: 4432 attributes.mFontFeatureSettings = appearance.getString(attr); 4433 break; 4434 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings: 4435 attributes.mFontVariationSettings = appearance.getString(attr); 4436 break; 4437 case com.android.internal.R.styleable.TextAppearance_lineBreakStyle: 4438 attributes.mHasLineBreakStyle = true; 4439 attributes.mLineBreakStyle = 4440 appearance.getInt(attr, attributes.mLineBreakStyle); 4441 break; 4442 case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle: 4443 attributes.mHasLineBreakWordStyle = true; 4444 attributes.mLineBreakWordStyle = 4445 appearance.getInt(attr, attributes.mLineBreakWordStyle); 4446 break; 4447 default: 4448 } 4449 } 4450 } 4451 applyTextAppearance(TextAppearanceAttributes attributes)4452 private void applyTextAppearance(TextAppearanceAttributes attributes) { 4453 if (attributes.mTextColor != null) { 4454 setTextColor(attributes.mTextColor); 4455 } 4456 4457 if (attributes.mTextColorHint != null) { 4458 setHintTextColor(attributes.mTextColorHint); 4459 } 4460 4461 if (attributes.mTextColorLink != null) { 4462 setLinkTextColor(attributes.mTextColorLink); 4463 } 4464 4465 if (attributes.mTextColorHighlight != 0) { 4466 setHighlightColor(attributes.mTextColorHighlight); 4467 } 4468 4469 if (attributes.mSearchResultHighlightColor != 0) { 4470 setSearchResultHighlightColor(attributes.mSearchResultHighlightColor); 4471 } 4472 4473 if (attributes.mFocusedSearchResultHighlightColor != 0) { 4474 setFocusedSearchResultHighlightColor(attributes.mFocusedSearchResultHighlightColor); 4475 } 4476 4477 if (attributes.mTextSize != -1) { 4478 mTextSizeUnit = attributes.mTextSizeUnit; 4479 setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */); 4480 } 4481 4482 if (attributes.mTextLocales != null) { 4483 setTextLocales(attributes.mTextLocales); 4484 } 4485 4486 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4487 attributes.mFontFamily = null; 4488 } 4489 setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily, 4490 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight); 4491 4492 if (attributes.mShadowColor != 0) { 4493 setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy, 4494 attributes.mShadowColor); 4495 } 4496 4497 if (attributes.mAllCaps) { 4498 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 4499 } 4500 4501 if (attributes.mHasElegant) { 4502 setElegantTextHeight(attributes.mElegant); 4503 } 4504 4505 if (attributes.mHasFallbackLineSpacing) { 4506 setFallbackLineSpacing(attributes.mFallbackLineSpacing); 4507 } 4508 4509 if (attributes.mHasLetterSpacing) { 4510 setLetterSpacing(attributes.mLetterSpacing); 4511 } 4512 4513 if (attributes.mFontFeatureSettings != null) { 4514 setFontFeatureSettings(attributes.mFontFeatureSettings); 4515 } 4516 4517 if (attributes.mFontVariationSettings != null) { 4518 setFontVariationSettings(attributes.mFontVariationSettings); 4519 } 4520 4521 if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) { 4522 updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle, 4523 attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle, 4524 attributes.mLineBreakWordStyle); 4525 } 4526 } 4527 4528 /** 4529 * Updates the LineBreakConfig from the TextAppearance. 4530 * 4531 * This method updates the given line configuration from the TextAppearance. This method will 4532 * request new layout if line break config has been changed. 4533 * 4534 * @param isLineBreakStyleSpecified true if the line break style is specified. 4535 * @param isLineBreakWordStyleSpecified true if the line break word style is specified. 4536 * @param lineBreakStyle the value of the line break style in the TextAppearance. 4537 * @param lineBreakWordStyle the value of the line break word style in the TextAppearance. 4538 */ updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified, boolean isLineBreakWordStyleSpecified, @LineBreakConfig.LineBreakStyle int lineBreakStyle, @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)4539 private void updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified, 4540 boolean isLineBreakWordStyleSpecified, 4541 @LineBreakConfig.LineBreakStyle int lineBreakStyle, 4542 @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { 4543 boolean updated = false; 4544 if (isLineBreakStyleSpecified && mLineBreakStyle != lineBreakStyle) { 4545 mLineBreakStyle = lineBreakStyle; 4546 updated = true; 4547 } 4548 if (isLineBreakWordStyleSpecified && mLineBreakWordStyle != lineBreakWordStyle) { 4549 mLineBreakWordStyle = lineBreakWordStyle; 4550 updated = true; 4551 } 4552 if (updated && mLayout != null) { 4553 nullLayouts(); 4554 requestLayout(); 4555 invalidate(); 4556 } 4557 } 4558 /** 4559 * Get the default primary {@link Locale} of the text in this TextView. This will always be 4560 * the first member of {@link #getTextLocales()}. 4561 * @return the default primary {@link Locale} of the text in this TextView. 4562 */ 4563 @NonNull getTextLocale()4564 public Locale getTextLocale() { 4565 return mTextPaint.getTextLocale(); 4566 } 4567 4568 /** 4569 * Get the default {@link LocaleList} of the text in this TextView. 4570 * @return the default {@link LocaleList} of the text in this TextView. 4571 */ 4572 @NonNull @Size(min = 1) getTextLocales()4573 public LocaleList getTextLocales() { 4574 return mTextPaint.getTextLocales(); 4575 } 4576 changeListenerLocaleTo(@ullable Locale locale)4577 private void changeListenerLocaleTo(@Nullable Locale locale) { 4578 if (mListenerChanged) { 4579 // If a listener has been explicitly set, don't change it. We may break something. 4580 return; 4581 } 4582 // The following null check is not absolutely necessary since all calling points of 4583 // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left 4584 // here in case others would want to call this method in the future. 4585 if (mEditor != null) { 4586 KeyListener listener = mEditor.mKeyListener; 4587 if (listener instanceof DigitsKeyListener) { 4588 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener); 4589 } else if (listener instanceof DateKeyListener) { 4590 listener = DateKeyListener.getInstance(locale); 4591 } else if (listener instanceof TimeKeyListener) { 4592 listener = TimeKeyListener.getInstance(locale); 4593 } else if (listener instanceof DateTimeKeyListener) { 4594 listener = DateTimeKeyListener.getInstance(locale); 4595 } else { 4596 return; 4597 } 4598 final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType); 4599 setKeyListenerOnly(listener); 4600 setInputTypeFromEditor(); 4601 if (wasPasswordType) { 4602 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS; 4603 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) { 4604 mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 4605 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) { 4606 mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD; 4607 } 4608 } 4609 } 4610 } 4611 4612 /** 4613 * Set the default {@link Locale} of the text in this TextView to a one-member 4614 * {@link LocaleList} containing just the given Locale. 4615 * 4616 * @param locale the {@link Locale} for drawing text, must not be null. 4617 * 4618 * @see #setTextLocales 4619 */ setTextLocale(@onNull Locale locale)4620 public void setTextLocale(@NonNull Locale locale) { 4621 mLocalesChanged = true; 4622 mTextPaint.setTextLocale(locale); 4623 if (mLayout != null) { 4624 nullLayouts(); 4625 requestLayout(); 4626 invalidate(); 4627 } 4628 } 4629 4630 /** 4631 * Set the default {@link LocaleList} of the text in this TextView to the given value. 4632 * 4633 * This value is used to choose appropriate typefaces for ambiguous characters (typically used 4634 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects 4635 * other aspects of text display, including line breaking. 4636 * 4637 * @param locales the {@link LocaleList} for drawing text, must not be null or empty. 4638 * 4639 * @see Paint#setTextLocales 4640 */ setTextLocales(@onNull @izemin = 1) LocaleList locales)4641 public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) { 4642 mLocalesChanged = true; 4643 mTextPaint.setTextLocales(locales); 4644 if (mLayout != null) { 4645 nullLayouts(); 4646 requestLayout(); 4647 invalidate(); 4648 } 4649 } 4650 4651 @Override onConfigurationChanged(Configuration newConfig)4652 protected void onConfigurationChanged(Configuration newConfig) { 4653 super.onConfigurationChanged(newConfig); 4654 if (!mLocalesChanged) { 4655 mTextPaint.setTextLocales(LocaleList.getDefault()); 4656 if (mLayout != null) { 4657 nullLayouts(); 4658 requestLayout(); 4659 invalidate(); 4660 } 4661 } 4662 if (mFontWeightAdjustment != newConfig.fontWeightAdjustment) { 4663 mFontWeightAdjustment = newConfig.fontWeightAdjustment; 4664 setTypeface(getTypeface()); 4665 } 4666 4667 InputMethodManager imm = getInputMethodManager(); 4668 // if orientation changed and this TextView is currently served. 4669 if (mLastOrientation != newConfig.orientation 4670 && imm != null && imm.hasActiveInputConnection(this)) { 4671 // EditorInfo.internalImeOptions are out of date. 4672 imm.restartInput(this); 4673 } 4674 mLastOrientation = newConfig.orientation; 4675 } 4676 4677 /** 4678 * @return the size (in pixels) of the default text size in this TextView. 4679 */ 4680 @InspectableProperty 4681 @ViewDebug.ExportedProperty(category = "text") getTextSize()4682 public float getTextSize() { 4683 return mTextPaint.getTextSize(); 4684 } 4685 4686 /** 4687 * @return the size (in scaled pixels) of the default text size in this TextView. 4688 * @hide 4689 */ 4690 @ViewDebug.ExportedProperty(category = "text") getScaledTextSize()4691 public float getScaledTextSize() { 4692 return mTextPaint.getTextSize() / mTextPaint.density; 4693 } 4694 4695 /** @hide */ 4696 @ViewDebug.ExportedProperty(category = "text", mapping = { 4697 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"), 4698 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"), 4699 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"), 4700 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") 4701 }) getTypefaceStyle()4702 public int getTypefaceStyle() { 4703 Typeface typeface = mTextPaint.getTypeface(); 4704 return typeface != null ? typeface.getStyle() : Typeface.NORMAL; 4705 } 4706 4707 /** 4708 * Set the default text size to the given value, interpreted as "scaled 4709 * pixel" units. This size is adjusted based on the current density and 4710 * user font size preference. 4711 * 4712 * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op. 4713 * 4714 * @param size The scaled pixel size. 4715 * 4716 * @attr ref android.R.styleable#TextView_textSize 4717 */ 4718 @android.view.RemotableViewMethod setTextSize(float size)4719 public void setTextSize(float size) { 4720 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 4721 } 4722 4723 /** 4724 * Set the default text size to a given unit and value. See {@link 4725 * TypedValue} for the possible dimension units. 4726 * 4727 * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op. 4728 * 4729 * @param unit The desired dimension unit. 4730 * @param size The desired size in the given units. 4731 * 4732 * @attr ref android.R.styleable#TextView_textSize 4733 */ setTextSize(int unit, float size)4734 public void setTextSize(int unit, float size) { 4735 if (!isAutoSizeEnabled()) { 4736 setTextSizeInternal(unit, size, true /* shouldRequestLayout */); 4737 } 4738 } 4739 4740 @NonNull getDisplayMetricsOrSystem()4741 private DisplayMetrics getDisplayMetricsOrSystem() { 4742 Context c = getContext(); 4743 Resources r; 4744 4745 if (c == null) { 4746 r = Resources.getSystem(); 4747 } else { 4748 r = c.getResources(); 4749 } 4750 4751 return r.getDisplayMetrics(); 4752 } 4753 setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4754 private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) { 4755 mTextSizeUnit = unit; 4756 setRawTextSize(TypedValue.applyDimension(unit, size, getDisplayMetricsOrSystem()), 4757 shouldRequestLayout); 4758 } 4759 4760 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) setRawTextSize(float size, boolean shouldRequestLayout)4761 private void setRawTextSize(float size, boolean shouldRequestLayout) { 4762 if (size != mTextPaint.getTextSize()) { 4763 mTextPaint.setTextSize(size); 4764 4765 maybeRecalculateLineHeight(); 4766 if (shouldRequestLayout && mLayout != null) { 4767 // Do not auto-size right after setting the text size. 4768 mNeedsAutoSizeText = false; 4769 nullLayouts(); 4770 requestLayout(); 4771 invalidate(); 4772 } 4773 } 4774 } 4775 4776 /** 4777 * Gets the text size unit defined by the developer. It may be specified in resources or be 4778 * passed as the unit argument of {@link #setTextSize(int, float)} at runtime. 4779 * 4780 * @return the dimension type of the text size unit originally defined. 4781 * @see TypedValue#TYPE_DIMENSION 4782 */ getTextSizeUnit()4783 public int getTextSizeUnit() { 4784 return mTextSizeUnit; 4785 } 4786 4787 /** 4788 * Gets the extent by which text should be stretched horizontally. 4789 * This will usually be 1.0. 4790 * @return The horizontal scale factor. 4791 */ 4792 @InspectableProperty getTextScaleX()4793 public float getTextScaleX() { 4794 return mTextPaint.getTextScaleX(); 4795 } 4796 4797 /** 4798 * Sets the horizontal scale factor for text. The default value 4799 * is 1.0. Values greater than 1.0 stretch the text wider. 4800 * Values less than 1.0 make the text narrower. By default, this value is 1.0. 4801 * @param size The horizontal scale factor. 4802 * @attr ref android.R.styleable#TextView_textScaleX 4803 */ 4804 @android.view.RemotableViewMethod setTextScaleX(float size)4805 public void setTextScaleX(float size) { 4806 if (size != mTextPaint.getTextScaleX()) { 4807 mUserSetTextScaleX = true; 4808 mTextPaint.setTextScaleX(size); 4809 4810 if (mLayout != null) { 4811 nullLayouts(); 4812 requestLayout(); 4813 invalidate(); 4814 } 4815 } 4816 } 4817 4818 /** 4819 * Sets the typeface and style in which the text should be displayed. 4820 * Note that not all Typeface families actually have bold and italic 4821 * variants, so you may need to use 4822 * {@link #setTypeface(Typeface, int)} to get the appearance 4823 * that you actually want. 4824 * 4825 * @see #getTypeface() 4826 * 4827 * @attr ref android.R.styleable#TextView_fontFamily 4828 * @attr ref android.R.styleable#TextView_typeface 4829 * @attr ref android.R.styleable#TextView_textStyle 4830 */ setTypeface(@ullable Typeface tf)4831 public void setTypeface(@Nullable Typeface tf) { 4832 mOriginalTypeface = tf; 4833 if (mFontWeightAdjustment != 0 4834 && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) { 4835 if (tf == null) { 4836 if (Flags.fixNullTypefaceBolding()) { 4837 tf = Typeface.DEFAULT_BOLD; 4838 } else { 4839 tf = Typeface.DEFAULT; 4840 } 4841 } else { 4842 int newWeight = Math.min( 4843 Math.max(tf.getWeight() + mFontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN), 4844 FontStyle.FONT_WEIGHT_MAX); 4845 int typefaceStyle = tf != null ? tf.getStyle() : 0; 4846 boolean italic = (typefaceStyle & Typeface.ITALIC) != 0; 4847 tf = Typeface.create(tf, newWeight, italic); 4848 } 4849 } 4850 if (mTextPaint.getTypeface() != tf) { 4851 mTextPaint.setTypeface(tf); 4852 4853 if (mLayout != null) { 4854 nullLayouts(); 4855 requestLayout(); 4856 invalidate(); 4857 } 4858 } 4859 } 4860 4861 /** 4862 * Gets the current {@link Typeface} that is used to style the text. 4863 * @return The current Typeface. 4864 * 4865 * @see #setTypeface(Typeface) 4866 * 4867 * @attr ref android.R.styleable#TextView_fontFamily 4868 * @attr ref android.R.styleable#TextView_typeface 4869 * @attr ref android.R.styleable#TextView_textStyle 4870 */ 4871 @InspectableProperty getTypeface()4872 public Typeface getTypeface() { 4873 return mOriginalTypeface; 4874 } 4875 4876 /** 4877 * Set the TextView's elegant height metrics flag. This setting selects font 4878 * variants that have not been compacted to fit Latin-based vertical 4879 * metrics, and also increases top and bottom bounds to provide more space. 4880 * 4881 * @param elegant set the paint's elegant metrics flag. 4882 * 4883 * @see #isElegantTextHeight() 4884 * @see Paint#isElegantTextHeight() 4885 * 4886 * @attr ref android.R.styleable#TextView_elegantTextHeight 4887 */ setElegantTextHeight(boolean elegant)4888 public void setElegantTextHeight(boolean elegant) { 4889 if (elegant != mTextPaint.isElegantTextHeight()) { 4890 mTextPaint.setElegantTextHeight(elegant); 4891 if (mLayout != null) { 4892 nullLayouts(); 4893 requestLayout(); 4894 invalidate(); 4895 } 4896 } 4897 } 4898 4899 /** 4900 * Set whether to respect the ascent and descent of the fallback fonts that are used in 4901 * displaying the text (which is needed to avoid text from consecutive lines running into 4902 * each other). If set, fallback fonts that end up getting used can increase the ascent 4903 * and descent of the lines that they are used on. 4904 * <p/> 4905 * It is required to be true if text could be in languages like Burmese or Tibetan where text 4906 * is typically much taller or deeper than Latin text. 4907 * 4908 * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default 4909 * 4910 * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean) 4911 * 4912 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4913 */ setFallbackLineSpacing(boolean enabled)4914 public void setFallbackLineSpacing(boolean enabled) { 4915 int fallbackStrategy; 4916 if (enabled) { 4917 if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) { 4918 fallbackStrategy = FALLBACK_LINE_SPACING_ALL; 4919 } else { 4920 fallbackStrategy = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; 4921 } 4922 } else { 4923 fallbackStrategy = FALLBACK_LINE_SPACING_NONE; 4924 } 4925 if (mUseFallbackLineSpacing != fallbackStrategy) { 4926 mUseFallbackLineSpacing = fallbackStrategy; 4927 if (mLayout != null) { 4928 nullLayouts(); 4929 requestLayout(); 4930 invalidate(); 4931 } 4932 } 4933 } 4934 4935 /** 4936 * Set true for using width of bounding box as a source of automatic line breaking and drawing. 4937 * 4938 * If this value is false, the TextView determines the View width, drawing offset and automatic 4939 * line breaking based on total advances as text widths. By setting true, use glyph bound's as a 4940 * source of text width. 4941 * 4942 * If the font used for this TextView has glyphs that has negative bearing X or glyph xMax is 4943 * greater than advance, the glyph clipping can be happened because the drawing area may be 4944 * bigger than advance. By setting this to true, the TextView will reserve more spaces for 4945 * drawing are, so clipping can be prevented. 4946 * 4947 * This value is true by default if the target API version is 35 or later. 4948 * 4949 * @param useBoundsForWidth true for using bounding box for width. false for using advances for 4950 * width. 4951 * @see #getUseBoundsForWidth() 4952 * @see #setShiftDrawingOffsetForStartOverhang(boolean) 4953 * @see #getShiftDrawingOffsetForStartOverhang() 4954 */ 4955 @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) setUseBoundsForWidth(boolean useBoundsForWidth)4956 public void setUseBoundsForWidth(boolean useBoundsForWidth) { 4957 if (mUseBoundsForWidth != useBoundsForWidth) { 4958 mUseBoundsForWidth = useBoundsForWidth; 4959 if (mLayout != null) { 4960 nullLayouts(); 4961 requestLayout(); 4962 invalidate(); 4963 } 4964 } 4965 } 4966 4967 /** 4968 * Returns true if using bounding box as a width, false for using advance as a width. 4969 * 4970 * @see #setUseBoundsForWidth(boolean) 4971 * @see #setShiftDrawingOffsetForStartOverhang(boolean) 4972 * @see #getShiftDrawingOffsetForStartOverhang() 4973 * @return True if using bounding box for width, false if using advance for width. 4974 */ 4975 @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) getUseBoundsForWidth()4976 public boolean getUseBoundsForWidth() { 4977 return mUseBoundsForWidth; 4978 } 4979 4980 /** 4981 * Set true for shifting the drawing x offset for showing overhang at the start position. 4982 * 4983 * This flag is ignored if the {@link #getUseBoundsForWidth()} is false. 4984 * 4985 * If this value is false, the TextView draws text from the zero even if there is a glyph stroke 4986 * in a region where the x coordinate is negative. TextView clips the stroke in the region where 4987 * the X coordinate is negative unless the parents has {@link ViewGroup#getClipChildren()} to 4988 * true. This is useful for aligning multiple TextViews vertically. 4989 * 4990 * If this value is true, the TextView draws text with shifting the x coordinate of the drawing 4991 * bounding box. This prevents the clipping even if the parents doesn't have 4992 * {@link ViewGroup#getClipChildren()} to true. 4993 * 4994 * This value is false by default. 4995 * 4996 * @param shiftDrawingOffsetForStartOverhang true for shifting the drawing offset for showing 4997 * the stroke that is in the region whre the x 4998 * coorinate is negative. 4999 * @see #setUseBoundsForWidth(boolean) 5000 * @see #getUseBoundsForWidth() 5001 */ 5002 @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) setShiftDrawingOffsetForStartOverhang(boolean shiftDrawingOffsetForStartOverhang)5003 public void setShiftDrawingOffsetForStartOverhang(boolean shiftDrawingOffsetForStartOverhang) { 5004 if (mShiftDrawingOffsetForStartOverhang != shiftDrawingOffsetForStartOverhang) { 5005 mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang; 5006 if (mLayout != null) { 5007 nullLayouts(); 5008 requestLayout(); 5009 invalidate(); 5010 } 5011 } 5012 } 5013 5014 /** 5015 * Returns true if shifting the drawing x offset for start overhang. 5016 * 5017 * @see #setShiftDrawingOffsetForStartOverhang(boolean) 5018 * @see #setUseBoundsForWidth(boolean) 5019 * @see #getUseBoundsForWidth() 5020 * @return True if shifting the drawing x offset for start overhang. 5021 */ 5022 @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) getShiftDrawingOffsetForStartOverhang()5023 public boolean getShiftDrawingOffsetForStartOverhang() { 5024 return mShiftDrawingOffsetForStartOverhang; 5025 } 5026 5027 /** 5028 * Set the minimum font metrics used for line spacing. 5029 * 5030 * <p> 5031 * {@code null} is the default value. If {@code null} is set or left as default, the font 5032 * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used. 5033 * 5034 * <p> 5035 * The minimum meaning here is the minimum value of line spacing: maximum value of 5036 * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}. 5037 * 5038 * <p> 5039 * By setting this value, each line will have minimum line spacing regardless of the text 5040 * rendered. For example, usually Japanese script has larger vertical metrics than Latin script. 5041 * By setting the metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} 5042 * for Japanese or leave it {@code null} if the TextView's locale or system locale is Japanese, 5043 * the line spacing for Japanese is reserved if the TextView contains English text. If the 5044 * vertical metrics of the text is larger than Japanese, for example Burmese, the bigger font 5045 * metrics is used. 5046 * 5047 * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the value 5048 * obtained by 5049 * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} 5050 * @see #getMinimumFontMetrics() 5051 * @see Layout#getMinimumFontMetrics() 5052 * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics) 5053 * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics) 5054 * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics) 5055 */ 5056 @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) setMinimumFontMetrics(@ullable Paint.FontMetrics minimumFontMetrics)5057 public void setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) { 5058 mMinimumFontMetrics = minimumFontMetrics; 5059 } 5060 5061 /** 5062 * Get the minimum font metrics used for line spacing. 5063 * 5064 * @see #setMinimumFontMetrics(Paint.FontMetrics) 5065 * @see Layout#getMinimumFontMetrics() 5066 * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics) 5067 * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics) 5068 * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics) 5069 * 5070 * @return a minimum font metrics. {@code null} for using the value obtained by 5071 * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} 5072 */ 5073 @Nullable 5074 @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) getMinimumFontMetrics()5075 public Paint.FontMetrics getMinimumFontMetrics() { 5076 return mMinimumFontMetrics; 5077 } 5078 5079 /** 5080 * Returns true if the locale preferred line height is used for the minimum line height. 5081 * 5082 * @return true if using locale preferred line height for the minimum line height. Otherwise 5083 * false. 5084 * 5085 * @see #setLocalePreferredLineHeightForMinimumUsed(boolean) 5086 * @see #setMinimumFontMetrics(Paint.FontMetrics) 5087 * @see #getMinimumFontMetrics() 5088 */ 5089 @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) isLocalePreferredLineHeightForMinimumUsed()5090 public boolean isLocalePreferredLineHeightForMinimumUsed() { 5091 return mUseLocalePreferredLineHeightForMinimum; 5092 } 5093 5094 /** 5095 * Set true if the locale preferred line height is used for the minimum line height. 5096 * 5097 * By setting this flag to true is equivalenet to call 5098 * {@link #setMinimumFontMetrics(Paint.FontMetrics)} with the one obtained by 5099 * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}. 5100 * 5101 * If custom minimum line height was specified by 5102 * {@link #setMinimumFontMetrics(Paint.FontMetrics)}, this flag will be ignored. 5103 * 5104 * @param flag true for using locale preferred line height for the minimum line height. 5105 * @see #isLocalePreferredLineHeightForMinimumUsed() 5106 * @see #setMinimumFontMetrics(Paint.FontMetrics) 5107 * @see #getMinimumFontMetrics() 5108 */ 5109 @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) setLocalePreferredLineHeightForMinimumUsed(boolean flag)5110 public void setLocalePreferredLineHeightForMinimumUsed(boolean flag) { 5111 mUseLocalePreferredLineHeightForMinimum = flag; 5112 } 5113 5114 /** 5115 * @return whether fallback line spacing is enabled, {@code true} by default 5116 * 5117 * @see #setFallbackLineSpacing(boolean) 5118 * 5119 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 5120 */ 5121 @InspectableProperty isFallbackLineSpacing()5122 public boolean isFallbackLineSpacing() { 5123 return mUseFallbackLineSpacing != FALLBACK_LINE_SPACING_NONE; 5124 } 5125 isFallbackLineSpacingForBoringLayout()5126 private boolean isFallbackLineSpacingForBoringLayout() { 5127 return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL; 5128 } 5129 5130 // Package privte for accessing from Editor.java isFallbackLineSpacingForStaticLayout()5131 /* package */ boolean isFallbackLineSpacingForStaticLayout() { 5132 return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL 5133 || mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; 5134 } 5135 5136 /** 5137 * Get the value of the TextView's elegant height metrics flag. This setting selects font 5138 * variants that have not been compacted to fit Latin-based vertical 5139 * metrics, and also increases top and bottom bounds to provide more space. 5140 * @return {@code true} if the elegant height metrics flag is set. 5141 * 5142 * @see #setElegantTextHeight(boolean) 5143 * @see Paint#setElegantTextHeight(boolean) 5144 */ 5145 @InspectableProperty isElegantTextHeight()5146 public boolean isElegantTextHeight() { 5147 return mTextPaint.isElegantTextHeight(); 5148 } 5149 5150 /** 5151 * Gets the text letter-space value, which determines the spacing between characters. 5152 * The value returned is in ems. Normally, this value is 0.0. 5153 * @return The text letter-space value in ems. 5154 * 5155 * @see #setLetterSpacing(float) 5156 * @see Paint#setLetterSpacing 5157 */ 5158 @InspectableProperty getLetterSpacing()5159 public float getLetterSpacing() { 5160 return mTextPaint.getLetterSpacing(); 5161 } 5162 5163 /** 5164 * Sets text letter-spacing in em units. Typical values 5165 * for slight expansion will be around 0.05. Negative values tighten text. 5166 * 5167 * @see #getLetterSpacing() 5168 * @see Paint#getLetterSpacing 5169 * 5170 * @param letterSpacing A text letter-space value in ems. 5171 * @attr ref android.R.styleable#TextView_letterSpacing 5172 */ 5173 @android.view.RemotableViewMethod setLetterSpacing(float letterSpacing)5174 public void setLetterSpacing(float letterSpacing) { 5175 if (letterSpacing != mTextPaint.getLetterSpacing()) { 5176 mTextPaint.setLetterSpacing(letterSpacing); 5177 5178 if (mLayout != null) { 5179 nullLayouts(); 5180 requestLayout(); 5181 invalidate(); 5182 } 5183 } 5184 } 5185 5186 /** 5187 * Returns the font feature settings. The format is the same as the CSS 5188 * font-feature-settings attribute: 5189 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 5190 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 5191 * 5192 * @return the currently set font feature settings. Default is null. 5193 * 5194 * @see #setFontFeatureSettings(String) 5195 * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String) 5196 */ 5197 @InspectableProperty 5198 @Nullable getFontFeatureSettings()5199 public String getFontFeatureSettings() { 5200 return mTextPaint.getFontFeatureSettings(); 5201 } 5202 5203 /** 5204 * Returns the font variation settings. 5205 * 5206 * @return the currently set font variation settings. Returns null if no variation is 5207 * specified. 5208 * 5209 * @see #setFontVariationSettings(String) 5210 * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String) 5211 */ 5212 @Nullable getFontVariationSettings()5213 public String getFontVariationSettings() { 5214 return mTextPaint.getFontVariationSettings(); 5215 } 5216 5217 /** 5218 * Sets the break strategy for breaking paragraphs into lines. The default value for 5219 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for 5220 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the 5221 * text "dancing" when being edited. 5222 * <p> 5223 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 5224 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 5225 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 5226 * improves the structure of text layout however has performance impact and requires more time 5227 * to do the text layout.</p> 5228 * <p> 5229 * Compared with {@link #setLineBreakStyle(int)}, line break style with different strictness is 5230 * evaluated in the ICU to identify the potential breakpoints. In 5231 * {@link #setBreakStrategy(int)}, line break strategy handles the post processing of ICU's line 5232 * break result. It aims to evaluate ICU's breakpoints and break the lines based on the 5233 * constraint. 5234 * </p> 5235 * 5236 * @attr ref android.R.styleable#TextView_breakStrategy 5237 * @see #getBreakStrategy() 5238 * @see #setHyphenationFrequency(int) 5239 */ setBreakStrategy(@ayout.BreakStrategy int breakStrategy)5240 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) { 5241 mBreakStrategy = breakStrategy; 5242 if (mLayout != null) { 5243 nullLayouts(); 5244 requestLayout(); 5245 invalidate(); 5246 } 5247 } 5248 5249 /** 5250 * Gets the current strategy for breaking paragraphs into lines. 5251 * @return the current strategy for breaking paragraphs into lines. 5252 * 5253 * @attr ref android.R.styleable#TextView_breakStrategy 5254 * @see #setBreakStrategy(int) 5255 */ 5256 @InspectableProperty(enumMapping = { 5257 @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE), 5258 @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY), 5259 @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED) 5260 }) 5261 @Layout.BreakStrategy getBreakStrategy()5262 public int getBreakStrategy() { 5263 return mBreakStrategy; 5264 } 5265 5266 /** 5267 * Sets the frequency of automatic hyphenation to use when determining word breaks. 5268 * The default value for both TextView and {@link EditText} is 5269 * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value 5270 * is set from the theme. 5271 * <p/> 5272 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 5273 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 5274 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 5275 * improves the structure of text layout however has performance impact and requires more time 5276 * to do the text layout. 5277 * <p/> 5278 * Note: Before Android Q, in the theme hyphenation frequency is set to 5279 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into 5280 * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q. 5281 * 5282 * @param hyphenationFrequency the hyphenation frequency to use, one of 5283 * {@link Layout#HYPHENATION_FREQUENCY_NONE}, 5284 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}, 5285 * {@link Layout#HYPHENATION_FREQUENCY_FULL} 5286 * @attr ref android.R.styleable#TextView_hyphenationFrequency 5287 * @see #getHyphenationFrequency() 5288 * @see #getBreakStrategy() 5289 */ setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)5290 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) { 5291 mHyphenationFrequency = hyphenationFrequency; 5292 if (mLayout != null) { 5293 nullLayouts(); 5294 requestLayout(); 5295 invalidate(); 5296 } 5297 } 5298 5299 /** 5300 * Gets the current frequency of automatic hyphenation to be used when determining word breaks. 5301 * @return the current frequency of automatic hyphenation to be used when determining word 5302 * breaks. 5303 * 5304 * @attr ref android.R.styleable#TextView_hyphenationFrequency 5305 * @see #setHyphenationFrequency(int) 5306 */ 5307 @InspectableProperty(enumMapping = { 5308 @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE), 5309 @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL), 5310 @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL) 5311 }) 5312 @Layout.HyphenationFrequency getHyphenationFrequency()5313 public int getHyphenationFrequency() { 5314 return mHyphenationFrequency; 5315 } 5316 5317 /** 5318 * Sets the line-break style for text wrapping. 5319 * 5320 * <p>Line-break style specifies the line-break strategies that can be used 5321 * for text wrapping. The line-break style affects rule-based line breaking 5322 * by specifying the strictness of line-breaking rules. 5323 * 5324 * <p>The following are types of line-break styles: 5325 * <ul> 5326 * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE} 5327 * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} 5328 * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT} 5329 * </ul> 5330 * 5331 * <p>The default line-break style is 5332 * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no 5333 * line-breaking rules are used. 5334 * 5335 * <p>See the 5336 * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external"> 5337 * line-break property</a> for more information. 5338 * 5339 * @param lineBreakStyle The line-break style for the text. 5340 */ setLineBreakStyle(@ineBreakConfig.LineBreakStyle int lineBreakStyle)5341 public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) { 5342 if (mLineBreakStyle != lineBreakStyle) { 5343 mLineBreakStyle = lineBreakStyle; 5344 if (mLayout != null) { 5345 nullLayouts(); 5346 requestLayout(); 5347 invalidate(); 5348 } 5349 } 5350 } 5351 5352 /** 5353 * Sets the line-break word style for text wrapping. 5354 * 5355 * <p>The line-break word style affects dictionary-based line breaking by 5356 * providing phrase-based line-breaking opportunities. Use 5357 * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify 5358 * phrase-based line breaking. 5359 * 5360 * <p>The default line-break word style is 5361 * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that 5362 * no line-breaking word style is used. 5363 * 5364 * <p>See the 5365 * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external"> 5366 * word-break property</a> for more information. 5367 * 5368 * @param lineBreakWordStyle The line-break word style for the text. 5369 */ setLineBreakWordStyle(@ineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)5370 public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { 5371 if (mLineBreakWordStyle != lineBreakWordStyle) { 5372 mLineBreakWordStyle = lineBreakWordStyle; 5373 if (mLayout != null) { 5374 nullLayouts(); 5375 requestLayout(); 5376 invalidate(); 5377 } 5378 } 5379 } 5380 5381 /** 5382 * Gets the current line-break style for text wrapping. 5383 * 5384 * @return The line-break style to be used for text wrapping. 5385 */ getLineBreakStyle()5386 public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() { 5387 return mLineBreakStyle; 5388 } 5389 5390 /** 5391 * Gets the current line-break word style for text wrapping. 5392 * 5393 * @return The line-break word style to be used for text wrapping. 5394 */ getLineBreakWordStyle()5395 public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() { 5396 return mLineBreakWordStyle; 5397 } 5398 5399 /** 5400 * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}. 5401 * 5402 * @return a current {@link PrecomputedText.Params} 5403 * @see PrecomputedText 5404 */ getTextMetricsParams()5405 public @NonNull PrecomputedText.Params getTextMetricsParams() { 5406 return new PrecomputedText.Params(new TextPaint(mTextPaint), 5407 LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle), 5408 getTextDirectionHeuristic(), 5409 mBreakStrategy, mHyphenationFrequency); 5410 } 5411 5412 /** 5413 * Apply the text layout parameter. 5414 * 5415 * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}. 5416 * @see PrecomputedText 5417 */ setTextMetricsParams(@onNull PrecomputedText.Params params)5418 public void setTextMetricsParams(@NonNull PrecomputedText.Params params) { 5419 mTextPaint.set(params.getTextPaint()); 5420 mUserSetTextScaleX = true; 5421 mTextDir = params.getTextDirection(); 5422 mBreakStrategy = params.getBreakStrategy(); 5423 mHyphenationFrequency = params.getHyphenationFrequency(); 5424 LineBreakConfig lineBreakConfig = params.getLineBreakConfig(); 5425 mLineBreakStyle = LineBreakConfig.getResolvedLineBreakStyle(lineBreakConfig); 5426 mLineBreakWordStyle = LineBreakConfig.getResolvedLineBreakWordStyle(lineBreakConfig); 5427 if (mLayout != null) { 5428 nullLayouts(); 5429 requestLayout(); 5430 invalidate(); 5431 } 5432 } 5433 5434 /** 5435 * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the 5436 * last line is too short for justification, the last line will be displayed with the 5437 * alignment set by {@link android.view.View#setTextAlignment}. 5438 * 5439 * @see #getJustificationMode() 5440 */ 5441 @Layout.JustificationMode 5442 @android.view.RemotableViewMethod setJustificationMode(@ayout.JustificationMode int justificationMode)5443 public void setJustificationMode(@Layout.JustificationMode int justificationMode) { 5444 mJustificationMode = justificationMode; 5445 if (mLayout != null) { 5446 nullLayouts(); 5447 requestLayout(); 5448 invalidate(); 5449 } 5450 } 5451 5452 /** 5453 * @return true if currently paragraph justification mode. 5454 * 5455 * @see #setJustificationMode(int) 5456 */ 5457 @InspectableProperty(enumMapping = { 5458 @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE), 5459 @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD) 5460 }) getJustificationMode()5461 public @Layout.JustificationMode int getJustificationMode() { 5462 return mJustificationMode; 5463 } 5464 5465 /** 5466 * Sets font feature settings. The format is the same as the CSS 5467 * font-feature-settings attribute: 5468 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 5469 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 5470 * 5471 * @param fontFeatureSettings font feature settings represented as CSS compatible string 5472 * 5473 * @see #getFontFeatureSettings() 5474 * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings() 5475 * 5476 * @attr ref android.R.styleable#TextView_fontFeatureSettings 5477 */ 5478 @android.view.RemotableViewMethod setFontFeatureSettings(@ullable String fontFeatureSettings)5479 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) { 5480 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) { 5481 mTextPaint.setFontFeatureSettings(fontFeatureSettings); 5482 5483 if (mLayout != null) { 5484 nullLayouts(); 5485 requestLayout(); 5486 invalidate(); 5487 } 5488 } 5489 } 5490 5491 5492 /** 5493 * Sets TrueType or OpenType font variation settings. The settings string is constructed from 5494 * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters 5495 * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that 5496 * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E 5497 * are invalid. If a specified axis name is not defined in the font, the settings will be 5498 * ignored. 5499 * 5500 * <p> 5501 * Examples, 5502 * <ul> 5503 * <li>Set font width to 150. 5504 * <pre> 5505 * <code> 5506 * TextView textView = (TextView) findViewById(R.id.textView); 5507 * textView.setFontVariationSettings("'wdth' 150"); 5508 * </code> 5509 * </pre> 5510 * </li> 5511 * 5512 * <li>Set the font slant to 20 degrees and ask for italic style. 5513 * <pre> 5514 * <code> 5515 * TextView textView = (TextView) findViewById(R.id.textView); 5516 * textView.setFontVariationSettings("'slnt' 20, 'ital' 1"); 5517 * </code> 5518 * </pre> 5519 * </p> 5520 * </li> 5521 * </ul> 5522 * 5523 * @param fontVariationSettings font variation settings. You can pass null or empty string as 5524 * no variation settings. 5525 * @return true if the given settings is effective to at least one font file underlying this 5526 * TextView. This function also returns true for empty settings string. Otherwise 5527 * returns false. 5528 * 5529 * @throws IllegalArgumentException If given string is not a valid font variation settings 5530 * format. 5531 * 5532 * @see #getFontVariationSettings() 5533 * @see FontVariationAxis 5534 * 5535 * @attr ref android.R.styleable#TextView_fontVariationSettings 5536 */ 5537 @android.view.RemotableViewMethod setFontVariationSettings(@ullable String fontVariationSettings)5538 public boolean setFontVariationSettings(@Nullable String fontVariationSettings) { 5539 final String existingSettings = mTextPaint.getFontVariationSettings(); 5540 if (fontVariationSettings == existingSettings 5541 || (fontVariationSettings != null 5542 && fontVariationSettings.equals(existingSettings))) { 5543 return true; 5544 } 5545 5546 boolean effective; 5547 if (Flags.typefaceRedesignReadonly()) { 5548 if (mFontWeightAdjustment != 0 5549 && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) { 5550 List<FontVariationAxis> axes = FontVariationAxis.fromFontVariationSettingsForList( 5551 fontVariationSettings); 5552 if (axes == null) { 5553 return false; // invalid format of the font variation settings. 5554 } 5555 boolean wghtAdjusted = false; 5556 for (int i = 0; i < axes.size(); ++i) { 5557 FontVariationAxis axis = axes.get(i); 5558 if (axis.getOpenTypeTagValue() == 0x77676874 /* wght */) { 5559 axes.set(i, new FontVariationAxis("wght", 5560 Math.clamp(axis.getStyleValue() + mFontWeightAdjustment, 5561 FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX))); 5562 wghtAdjusted = true; 5563 } 5564 } 5565 if (!wghtAdjusted) { 5566 axes.add(new FontVariationAxis("wght", 5567 Math.clamp(400 + mFontWeightAdjustment, 5568 FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX))); 5569 } 5570 mTextPaint.setFontVariationSettings( 5571 FontVariationAxis.toFontVariationSettings(axes)); 5572 } else { 5573 mTextPaint.setFontVariationSettings(fontVariationSettings); 5574 } 5575 effective = true; 5576 } else { 5577 effective = mTextPaint.setFontVariationSettings(fontVariationSettings); 5578 } 5579 5580 if (effective && mLayout != null) { 5581 nullLayouts(); 5582 requestLayout(); 5583 invalidate(); 5584 } 5585 return effective; 5586 } 5587 5588 /** 5589 * Sets the text color for all the states (normal, selected, 5590 * focused) to be this color. 5591 * 5592 * @param color A color value in the form 0xAARRGGBB. 5593 * Do not pass a resource ID. To get a color value from a resource ID, call 5594 * {@link androidx.core.content.ContextCompat#getColor(Context, int) getColor}. 5595 * 5596 * @see #setTextColor(ColorStateList) 5597 * @see #getTextColors() 5598 * 5599 * @attr ref android.R.styleable#TextView_textColor 5600 */ 5601 @android.view.RemotableViewMethod setTextColor(@olorInt int color)5602 public void setTextColor(@ColorInt int color) { 5603 mTextColor = ColorStateList.valueOf(color); 5604 updateTextColors(); 5605 } 5606 5607 /** 5608 * Sets the text color. 5609 * 5610 * @see #setTextColor(int) 5611 * @see #getTextColors() 5612 * @see #setHintTextColor(ColorStateList) 5613 * @see #setLinkTextColor(ColorStateList) 5614 * 5615 * @attr ref android.R.styleable#TextView_textColor 5616 */ 5617 @android.view.RemotableViewMethod setTextColor(ColorStateList colors)5618 public void setTextColor(ColorStateList colors) { 5619 if (colors == null) { 5620 throw new NullPointerException(); 5621 } 5622 5623 mTextColor = colors; 5624 updateTextColors(); 5625 } 5626 5627 /** 5628 * Gets the text colors for the different states (normal, selected, focused) of the TextView. 5629 * 5630 * @see #setTextColor(ColorStateList) 5631 * @see #setTextColor(int) 5632 * 5633 * @attr ref android.R.styleable#TextView_textColor 5634 */ 5635 @InspectableProperty(name = "textColor") getTextColors()5636 public final ColorStateList getTextColors() { 5637 return mTextColor; 5638 } 5639 5640 /** 5641 * Return the current color selected for normal text. 5642 * 5643 * @return Returns the current text color. 5644 */ 5645 @ColorInt getCurrentTextColor()5646 public final int getCurrentTextColor() { 5647 return mCurTextColor; 5648 } 5649 5650 /** 5651 * Sets the color used to display the selection highlight. 5652 * 5653 * @attr ref android.R.styleable#TextView_textColorHighlight 5654 */ 5655 @android.view.RemotableViewMethod setHighlightColor(@olorInt int color)5656 public void setHighlightColor(@ColorInt int color) { 5657 if (mHighlightColor != color) { 5658 mHighlightColor = color; 5659 invalidate(); 5660 } 5661 } 5662 5663 /** 5664 * @return the color used to display the selection highlight 5665 * 5666 * @see #setHighlightColor(int) 5667 * 5668 * @attr ref android.R.styleable#TextView_textColorHighlight 5669 */ 5670 @InspectableProperty(name = "textColorHighlight") 5671 @ColorInt getHighlightColor()5672 public int getHighlightColor() { 5673 return mHighlightColor; 5674 } 5675 5676 /** 5677 * Sets whether the soft input method will be made visible when this 5678 * TextView gets focused. The default is true. 5679 */ 5680 @android.view.RemotableViewMethod setShowSoftInputOnFocus(boolean show)5681 public final void setShowSoftInputOnFocus(boolean show) { 5682 createEditorIfNeeded(); 5683 mEditor.mShowSoftInputOnFocus = show; 5684 } 5685 5686 /** 5687 * Returns whether the soft input method will be made visible when this 5688 * TextView gets focused. The default is true. 5689 */ getShowSoftInputOnFocus()5690 public final boolean getShowSoftInputOnFocus() { 5691 // When there is no Editor, return default true value 5692 return mEditor == null || mEditor.mShowSoftInputOnFocus; 5693 } 5694 5695 /** 5696 * Gives the text a shadow of the specified blur radius and color, the specified 5697 * distance from its drawn position. 5698 * <p> 5699 * The text shadow produced does not interact with the properties on view 5700 * that are responsible for real time shadows, 5701 * {@link View#getElevation() elevation} and 5702 * {@link View#getTranslationZ() translationZ}. 5703 * 5704 * @see Paint#setShadowLayer(float, float, float, int) 5705 * 5706 * @attr ref android.R.styleable#TextView_shadowColor 5707 * @attr ref android.R.styleable#TextView_shadowDx 5708 * @attr ref android.R.styleable#TextView_shadowDy 5709 * @attr ref android.R.styleable#TextView_shadowRadius 5710 */ setShadowLayer(float radius, float dx, float dy, int color)5711 public void setShadowLayer(float radius, float dx, float dy, int color) { 5712 mTextPaint.setShadowLayer(radius, dx, dy, color); 5713 5714 mShadowRadius = radius; 5715 mShadowDx = dx; 5716 mShadowDy = dy; 5717 mShadowColor = color; 5718 5719 // Will change text clip region 5720 if (mEditor != null) { 5721 mEditor.invalidateTextDisplayList(); 5722 mEditor.invalidateHandlesAndActionMode(); 5723 } 5724 invalidate(); 5725 } 5726 5727 /** 5728 * Gets the radius of the shadow layer. 5729 * 5730 * @return the radius of the shadow layer. If 0, the shadow layer is not visible 5731 * 5732 * @see #setShadowLayer(float, float, float, int) 5733 * 5734 * @attr ref android.R.styleable#TextView_shadowRadius 5735 */ 5736 @InspectableProperty getShadowRadius()5737 public float getShadowRadius() { 5738 return mShadowRadius; 5739 } 5740 5741 /** 5742 * @return the horizontal offset of the shadow layer 5743 * 5744 * @see #setShadowLayer(float, float, float, int) 5745 * 5746 * @attr ref android.R.styleable#TextView_shadowDx 5747 */ 5748 @InspectableProperty getShadowDx()5749 public float getShadowDx() { 5750 return mShadowDx; 5751 } 5752 5753 /** 5754 * Gets the vertical offset of the shadow layer. 5755 * @return The vertical offset of the shadow layer. 5756 * 5757 * @see #setShadowLayer(float, float, float, int) 5758 * 5759 * @attr ref android.R.styleable#TextView_shadowDy 5760 */ 5761 @InspectableProperty getShadowDy()5762 public float getShadowDy() { 5763 return mShadowDy; 5764 } 5765 5766 /** 5767 * Gets the color of the shadow layer. 5768 * @return the color of the shadow layer 5769 * 5770 * @see #setShadowLayer(float, float, float, int) 5771 * 5772 * @attr ref android.R.styleable#TextView_shadowColor 5773 */ 5774 @InspectableProperty 5775 @ColorInt getShadowColor()5776 public int getShadowColor() { 5777 return mShadowColor; 5778 } 5779 5780 /** 5781 * Gets the {@link TextPaint} used for the text. 5782 * Use this only to consult the Paint's properties and not to change them. 5783 * @return The base paint used for the text. 5784 */ getPaint()5785 public TextPaint getPaint() { 5786 return mTextPaint; 5787 } 5788 5789 /** 5790 * Sets the autolink mask of the text. See {@link 5791 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 5792 * possible values. 5793 * 5794 * <p class="note"><b>Note:</b> 5795 * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES} 5796 * is deprecated and should be avoided; see its documentation. 5797 * 5798 * @attr ref android.R.styleable#TextView_autoLink 5799 */ 5800 @android.view.RemotableViewMethod setAutoLinkMask(int mask)5801 public final void setAutoLinkMask(int mask) { 5802 mAutoLinkMask = mask; 5803 } 5804 5805 /** 5806 * Sets whether the movement method will automatically be set to 5807 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 5808 * set to nonzero and links are detected in {@link #setText}. 5809 * The default is true. 5810 * 5811 * @attr ref android.R.styleable#TextView_linksClickable 5812 */ 5813 @android.view.RemotableViewMethod setLinksClickable(boolean whether)5814 public final void setLinksClickable(boolean whether) { 5815 mLinksClickable = whether; 5816 } 5817 5818 /** 5819 * Returns whether the movement method will automatically be set to 5820 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 5821 * set to nonzero and links are detected in {@link #setText}. 5822 * The default is true. 5823 * 5824 * @attr ref android.R.styleable#TextView_linksClickable 5825 */ 5826 @InspectableProperty getLinksClickable()5827 public final boolean getLinksClickable() { 5828 return mLinksClickable; 5829 } 5830 5831 /** 5832 * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text 5833 * (by {@link Linkify} or otherwise) if any. You can call 5834 * {@link URLSpan#getURL} on them to find where they link to 5835 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 5836 * to find the region of the text they are attached to. 5837 */ getUrls()5838 public URLSpan[] getUrls() { 5839 if (mText instanceof Spanned) { 5840 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 5841 } else { 5842 return new URLSpan[0]; 5843 } 5844 } 5845 5846 /** 5847 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this 5848 * TextView. 5849 * 5850 * @see #setHintTextColor(ColorStateList) 5851 * @see #getHintTextColors() 5852 * @see #setTextColor(int) 5853 * 5854 * @attr ref android.R.styleable#TextView_textColorHint 5855 */ 5856 @android.view.RemotableViewMethod setHintTextColor(@olorInt int color)5857 public final void setHintTextColor(@ColorInt int color) { 5858 mHintTextColor = ColorStateList.valueOf(color); 5859 updateTextColors(); 5860 } 5861 5862 /** 5863 * Sets the color of the hint text. 5864 * 5865 * @see #getHintTextColors() 5866 * @see #setHintTextColor(int) 5867 * @see #setTextColor(ColorStateList) 5868 * @see #setLinkTextColor(ColorStateList) 5869 * 5870 * @attr ref android.R.styleable#TextView_textColorHint 5871 */ setHintTextColor(ColorStateList colors)5872 public final void setHintTextColor(ColorStateList colors) { 5873 mHintTextColor = colors; 5874 updateTextColors(); 5875 } 5876 5877 /** 5878 * @return the color of the hint text, for the different states of this TextView. 5879 * 5880 * @see #setHintTextColor(ColorStateList) 5881 * @see #setHintTextColor(int) 5882 * @see #setTextColor(ColorStateList) 5883 * @see #setLinkTextColor(ColorStateList) 5884 * 5885 * @attr ref android.R.styleable#TextView_textColorHint 5886 */ 5887 @InspectableProperty(name = "textColorHint") getHintTextColors()5888 public final ColorStateList getHintTextColors() { 5889 return mHintTextColor; 5890 } 5891 5892 /** 5893 * <p>Return the current color selected to paint the hint text.</p> 5894 * 5895 * @return Returns the current hint text color. 5896 */ 5897 @ColorInt getCurrentHintTextColor()5898 public final int getCurrentHintTextColor() { 5899 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 5900 } 5901 5902 /** 5903 * Sets the color of links in the text. 5904 * 5905 * @see #setLinkTextColor(ColorStateList) 5906 * @see #getLinkTextColors() 5907 * 5908 * @attr ref android.R.styleable#TextView_textColorLink 5909 */ 5910 @android.view.RemotableViewMethod setLinkTextColor(@olorInt int color)5911 public final void setLinkTextColor(@ColorInt int color) { 5912 mLinkTextColor = ColorStateList.valueOf(color); 5913 updateTextColors(); 5914 } 5915 5916 /** 5917 * Sets the color of links in the text. 5918 * 5919 * @see #setLinkTextColor(int) 5920 * @see #getLinkTextColors() 5921 * @see #setTextColor(ColorStateList) 5922 * @see #setHintTextColor(ColorStateList) 5923 * 5924 * @attr ref android.R.styleable#TextView_textColorLink 5925 */ setLinkTextColor(ColorStateList colors)5926 public final void setLinkTextColor(ColorStateList colors) { 5927 mLinkTextColor = colors; 5928 updateTextColors(); 5929 } 5930 5931 /** 5932 * @return the list of colors used to paint the links in the text, for the different states of 5933 * this TextView 5934 * 5935 * @see #setLinkTextColor(ColorStateList) 5936 * @see #setLinkTextColor(int) 5937 * 5938 * @attr ref android.R.styleable#TextView_textColorLink 5939 */ 5940 @InspectableProperty(name = "textColorLink") getLinkTextColors()5941 public final ColorStateList getLinkTextColors() { 5942 return mLinkTextColor; 5943 } 5944 5945 /** 5946 * Sets the horizontal alignment of the text and the 5947 * vertical gravity that will be used when there is extra space 5948 * in the TextView beyond what is required for the text itself. 5949 * 5950 * @see android.view.Gravity 5951 * @attr ref android.R.styleable#TextView_gravity 5952 */ 5953 @android.view.RemotableViewMethod setGravity(int gravity)5954 public void setGravity(int gravity) { 5955 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 5956 gravity |= Gravity.START; 5957 } 5958 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 5959 gravity |= Gravity.TOP; 5960 } 5961 5962 boolean newLayout = false; 5963 5964 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) 5965 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) { 5966 newLayout = true; 5967 } 5968 5969 if (gravity != mGravity) { 5970 invalidate(); 5971 } 5972 5973 mGravity = gravity; 5974 5975 if (mLayout != null && newLayout) { 5976 // XXX this is heavy-handed because no actual content changes. 5977 int want = mLayout.getWidth(); 5978 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 5979 5980 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 5981 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true); 5982 } 5983 } 5984 5985 /** 5986 * Returns the horizontal and vertical alignment of this TextView. 5987 * 5988 * @see android.view.Gravity 5989 * @attr ref android.R.styleable#TextView_gravity 5990 */ 5991 @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY) getGravity()5992 public int getGravity() { 5993 return mGravity; 5994 } 5995 5996 /** 5997 * Gets the flags on the Paint being used to display the text. 5998 * @return The flags on the Paint being used to display the text. 5999 * @see Paint#getFlags 6000 */ getPaintFlags()6001 public int getPaintFlags() { 6002 return mTextPaint.getFlags(); 6003 } 6004 6005 /** 6006 * Sets flags on the Paint being used to display the text and 6007 * reflows the text if they are different from the old flags. 6008 * @see Paint#setFlags 6009 */ 6010 @android.view.RemotableViewMethod setPaintFlags(int flags)6011 public void setPaintFlags(int flags) { 6012 if (mTextPaint.getFlags() != flags) { 6013 mTextPaint.setFlags(flags); 6014 6015 if (mLayout != null) { 6016 nullLayouts(); 6017 requestLayout(); 6018 invalidate(); 6019 } 6020 } 6021 } 6022 6023 /** 6024 * Sets whether the text should be allowed to be wider than the 6025 * View is. If false, it will be wrapped to the width of the View. 6026 * 6027 * @attr ref android.R.styleable#TextView_scrollHorizontally 6028 */ setHorizontallyScrolling(boolean whether)6029 public void setHorizontallyScrolling(boolean whether) { 6030 if (mHorizontallyScrolling != whether) { 6031 mHorizontallyScrolling = whether; 6032 6033 if (mLayout != null) { 6034 nullLayouts(); 6035 requestLayout(); 6036 invalidate(); 6037 } 6038 } 6039 } 6040 6041 /** 6042 * Returns whether the text is allowed to be wider than the View. 6043 * If false, the text will be wrapped to the width of the View. 6044 * 6045 * @attr ref android.R.styleable#TextView_scrollHorizontally 6046 * @see #setHorizontallyScrolling(boolean) 6047 */ 6048 @InspectableProperty(name = "scrollHorizontally") isHorizontallyScrollable()6049 public final boolean isHorizontallyScrollable() { 6050 return mHorizontallyScrolling; 6051 } 6052 6053 /** 6054 * Returns whether the text is allowed to be wider than the View. 6055 * If false, the text will be wrapped to the width of the View. 6056 * 6057 * @attr ref android.R.styleable#TextView_scrollHorizontally 6058 * @hide 6059 */ 6060 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) getHorizontallyScrolling()6061 public boolean getHorizontallyScrolling() { 6062 return mHorizontallyScrolling; 6063 } 6064 6065 /** 6066 * Sets the height of the TextView to be at least {@code minLines} tall. 6067 * <p> 6068 * This value is used for height calculation if LayoutParams does not force TextView to have an 6069 * exact height. Setting this value overrides other previous minimum height configurations such 6070 * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set 6071 * this value to 1. 6072 * 6073 * @param minLines the minimum height of TextView in terms of number of lines 6074 * 6075 * @see #getMinLines() 6076 * @see #setLines(int) 6077 * 6078 * @attr ref android.R.styleable#TextView_minLines 6079 */ 6080 @android.view.RemotableViewMethod setMinLines(int minLines)6081 public void setMinLines(int minLines) { 6082 mMinimum = minLines; 6083 mMinMode = LINES; 6084 6085 requestLayout(); 6086 invalidate(); 6087 } 6088 6089 /** 6090 * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum 6091 * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}. 6092 * 6093 * @return the minimum height of TextView in terms of number of lines or -1 if the minimum 6094 * height is not defined in lines 6095 * 6096 * @see #setMinLines(int) 6097 * @see #setLines(int) 6098 * 6099 * @attr ref android.R.styleable#TextView_minLines 6100 */ 6101 @InspectableProperty getMinLines()6102 public int getMinLines() { 6103 return mMinMode == LINES ? mMinimum : -1; 6104 } 6105 6106 /** 6107 * Sets the height of the TextView to be at least {@code minPixels} tall. 6108 * <p> 6109 * This value is used for height calculation if LayoutParams does not force TextView to have an 6110 * exact height. Setting this value overrides previous minimum height configurations such as 6111 * {@link #setMinLines(int)} or {@link #setLines(int)}. 6112 * <p> 6113 * The value given here is different than {@link #setMinimumHeight(int)}. Between 6114 * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is 6115 * used to decide the final height. 6116 * 6117 * @param minPixels the minimum height of TextView in terms of pixels 6118 * 6119 * @see #getMinHeight() 6120 * @see #setHeight(int) 6121 * 6122 * @attr ref android.R.styleable#TextView_minHeight 6123 */ 6124 @android.view.RemotableViewMethod setMinHeight(int minPixels)6125 public void setMinHeight(int minPixels) { 6126 mMinimum = minPixels; 6127 mMinMode = PIXELS; 6128 6129 requestLayout(); 6130 invalidate(); 6131 } 6132 6133 /** 6134 * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was 6135 * set using {@link #setMinLines(int)} or {@link #setLines(int)}. 6136 * 6137 * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not 6138 * defined in pixels 6139 * 6140 * @see #setMinHeight(int) 6141 * @see #setHeight(int) 6142 * 6143 * @attr ref android.R.styleable#TextView_minHeight 6144 */ getMinHeight()6145 public int getMinHeight() { 6146 return mMinMode == PIXELS ? mMinimum : -1; 6147 } 6148 6149 /** 6150 * Sets the height of the TextView to be at most {@code maxLines} tall. 6151 * <p> 6152 * This value is used for height calculation if LayoutParams does not force TextView to have an 6153 * exact height. Setting this value overrides previous maximum height configurations such as 6154 * {@link #setMaxHeight(int)} or {@link #setLines(int)}. 6155 * 6156 * @param maxLines the maximum height of TextView in terms of number of lines 6157 * 6158 * @see #getMaxLines() 6159 * @see #setLines(int) 6160 * 6161 * @attr ref android.R.styleable#TextView_maxLines 6162 */ 6163 @android.view.RemotableViewMethod setMaxLines(int maxLines)6164 public void setMaxLines(int maxLines) { 6165 mMaximum = maxLines; 6166 mMaxMode = LINES; 6167 6168 requestLayout(); 6169 invalidate(); 6170 } 6171 6172 /** 6173 * Returns the maximum height of TextView in terms of number of lines or -1 if the 6174 * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}. 6175 * 6176 * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height 6177 * is not defined in lines. 6178 * 6179 * @see #setMaxLines(int) 6180 * @see #setLines(int) 6181 * 6182 * @attr ref android.R.styleable#TextView_maxLines 6183 */ 6184 @InspectableProperty getMaxLines()6185 public int getMaxLines() { 6186 return mMaxMode == LINES ? mMaximum : -1; 6187 } 6188 6189 /** 6190 * Sets the height of the TextView to be at most {@code maxPixels} tall. 6191 * <p> 6192 * This value is used for height calculation if LayoutParams does not force TextView to have an 6193 * exact height. Setting this value overrides previous maximum height configurations such as 6194 * {@link #setMaxLines(int)} or {@link #setLines(int)}. 6195 * 6196 * @param maxPixels the maximum height of TextView in terms of pixels 6197 * 6198 * @see #getMaxHeight() 6199 * @see #setHeight(int) 6200 * 6201 * @attr ref android.R.styleable#TextView_maxHeight 6202 */ 6203 @android.view.RemotableViewMethod setMaxHeight(int maxPixels)6204 public void setMaxHeight(int maxPixels) { 6205 mMaximum = maxPixels; 6206 mMaxMode = PIXELS; 6207 6208 requestLayout(); 6209 invalidate(); 6210 } 6211 6212 /** 6213 * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was 6214 * set using {@link #setMaxLines(int)} or {@link #setLines(int)}. 6215 * 6216 * @return the maximum height of TextView in terms of pixels or -1 if the maximum height 6217 * is not defined in pixels 6218 * 6219 * @see #setMaxHeight(int) 6220 * @see #setHeight(int) 6221 * 6222 * @attr ref android.R.styleable#TextView_maxHeight 6223 */ 6224 @InspectableProperty getMaxHeight()6225 public int getMaxHeight() { 6226 return mMaxMode == PIXELS ? mMaximum : -1; 6227 } 6228 6229 /** 6230 * Sets the height of the TextView to be exactly {@code lines} tall. 6231 * <p> 6232 * This value is used for height calculation if LayoutParams does not force TextView to have an 6233 * exact height. Setting this value overrides previous minimum/maximum height configurations 6234 * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will 6235 * set this value to 1. 6236 * 6237 * @param lines the exact height of the TextView in terms of lines 6238 * 6239 * @see #setHeight(int) 6240 * 6241 * @attr ref android.R.styleable#TextView_lines 6242 */ 6243 @android.view.RemotableViewMethod setLines(int lines)6244 public void setLines(int lines) { 6245 mMaximum = mMinimum = lines; 6246 mMaxMode = mMinMode = LINES; 6247 6248 requestLayout(); 6249 invalidate(); 6250 } 6251 6252 /** 6253 * Sets the height of the TextView to be exactly <code>pixels</code> tall. 6254 * <p> 6255 * This value is used for height calculation if LayoutParams does not force TextView to have an 6256 * exact height. Setting this value overrides previous minimum/maximum height configurations 6257 * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}. 6258 * 6259 * @param pixels the exact height of the TextView in terms of pixels 6260 * 6261 * @see #setLines(int) 6262 * 6263 * @attr ref android.R.styleable#TextView_height 6264 */ 6265 @android.view.RemotableViewMethod setHeight(int pixels)6266 public void setHeight(int pixels) { 6267 mMaximum = mMinimum = pixels; 6268 mMaxMode = mMinMode = PIXELS; 6269 6270 requestLayout(); 6271 invalidate(); 6272 } 6273 6274 /** 6275 * Sets the width of the TextView to be at least {@code minEms} wide. 6276 * <p> 6277 * This value is used for width calculation if LayoutParams does not force TextView to have an 6278 * exact width. Setting this value overrides previous minimum width configurations such as 6279 * {@link #setMinWidth(int)} or {@link #setWidth(int)}. 6280 * 6281 * @param minEms the minimum width of TextView in terms of ems 6282 * 6283 * @see #getMinEms() 6284 * @see #setEms(int) 6285 * 6286 * @attr ref android.R.styleable#TextView_minEms 6287 */ 6288 @android.view.RemotableViewMethod setMinEms(int minEms)6289 public void setMinEms(int minEms) { 6290 mMinWidth = minEms; 6291 mMinWidthMode = EMS; 6292 6293 requestLayout(); 6294 invalidate(); 6295 } 6296 6297 /** 6298 * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set 6299 * using {@link #setMinWidth(int)} or {@link #setWidth(int)}. 6300 * 6301 * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not 6302 * defined in ems 6303 * 6304 * @see #setMinEms(int) 6305 * @see #setEms(int) 6306 * 6307 * @attr ref android.R.styleable#TextView_minEms 6308 */ 6309 @InspectableProperty getMinEms()6310 public int getMinEms() { 6311 return mMinWidthMode == EMS ? mMinWidth : -1; 6312 } 6313 6314 /** 6315 * Sets the width of the TextView to be at least {@code minPixels} wide. 6316 * <p> 6317 * This value is used for width calculation if LayoutParams does not force TextView to have an 6318 * exact width. Setting this value overrides previous minimum width configurations such as 6319 * {@link #setMinEms(int)} or {@link #setEms(int)}. 6320 * <p> 6321 * The value given here is different than {@link #setMinimumWidth(int)}. Between 6322 * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used 6323 * to decide the final width. 6324 * 6325 * @param minPixels the minimum width of TextView in terms of pixels 6326 * 6327 * @see #getMinWidth() 6328 * @see #setWidth(int) 6329 * 6330 * @attr ref android.R.styleable#TextView_minWidth 6331 */ 6332 @android.view.RemotableViewMethod setMinWidth(int minPixels)6333 public void setMinWidth(int minPixels) { 6334 mMinWidth = minPixels; 6335 mMinWidthMode = PIXELS; 6336 6337 requestLayout(); 6338 invalidate(); 6339 } 6340 6341 /** 6342 * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set 6343 * using {@link #setMinEms(int)} or {@link #setEms(int)}. 6344 * 6345 * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not 6346 * defined in pixels 6347 * 6348 * @see #setMinWidth(int) 6349 * @see #setWidth(int) 6350 * 6351 * @attr ref android.R.styleable#TextView_minWidth 6352 */ 6353 @InspectableProperty getMinWidth()6354 public int getMinWidth() { 6355 return mMinWidthMode == PIXELS ? mMinWidth : -1; 6356 } 6357 6358 /** 6359 * Sets the width of the TextView to be at most {@code maxEms} wide. 6360 * <p> 6361 * This value is used for width calculation if LayoutParams does not force TextView to have an 6362 * exact width. Setting this value overrides previous maximum width configurations such as 6363 * {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 6364 * 6365 * @param maxEms the maximum width of TextView in terms of ems 6366 * 6367 * @see #getMaxEms() 6368 * @see #setEms(int) 6369 * 6370 * @attr ref android.R.styleable#TextView_maxEms 6371 */ 6372 @android.view.RemotableViewMethod setMaxEms(int maxEms)6373 public void setMaxEms(int maxEms) { 6374 mMaxWidth = maxEms; 6375 mMaxWidthMode = EMS; 6376 6377 requestLayout(); 6378 invalidate(); 6379 } 6380 6381 /** 6382 * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set 6383 * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 6384 * 6385 * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not 6386 * defined in ems 6387 * 6388 * @see #setMaxEms(int) 6389 * @see #setEms(int) 6390 * 6391 * @attr ref android.R.styleable#TextView_maxEms 6392 */ 6393 @InspectableProperty getMaxEms()6394 public int getMaxEms() { 6395 return mMaxWidthMode == EMS ? mMaxWidth : -1; 6396 } 6397 6398 /** 6399 * Sets the width of the TextView to be at most {@code maxPixels} wide. 6400 * <p> 6401 * This value is used for width calculation if LayoutParams does not force TextView to have an 6402 * exact width. Setting this value overrides previous maximum width configurations such as 6403 * {@link #setMaxEms(int)} or {@link #setEms(int)}. 6404 * 6405 * @param maxPixels the maximum width of TextView in terms of pixels 6406 * 6407 * @see #getMaxWidth() 6408 * @see #setWidth(int) 6409 * 6410 * @attr ref android.R.styleable#TextView_maxWidth 6411 */ 6412 @android.view.RemotableViewMethod setMaxWidth(int maxPixels)6413 public void setMaxWidth(int maxPixels) { 6414 mMaxWidth = maxPixels; 6415 mMaxWidthMode = PIXELS; 6416 6417 requestLayout(); 6418 invalidate(); 6419 } 6420 6421 /** 6422 * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set 6423 * using {@link #setMaxEms(int)} or {@link #setEms(int)}. 6424 * 6425 * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not 6426 * defined in pixels 6427 * 6428 * @see #setMaxWidth(int) 6429 * @see #setWidth(int) 6430 * 6431 * @attr ref android.R.styleable#TextView_maxWidth 6432 */ 6433 @InspectableProperty getMaxWidth()6434 public int getMaxWidth() { 6435 return mMaxWidthMode == PIXELS ? mMaxWidth : -1; 6436 } 6437 6438 /** 6439 * Sets the width of the TextView to be exactly {@code ems} wide. 6440 * 6441 * This value is used for width calculation if LayoutParams does not force TextView to have an 6442 * exact width. Setting this value overrides previous minimum/maximum configurations such as 6443 * {@link #setMinEms(int)} or {@link #setMaxEms(int)}. 6444 * 6445 * @param ems the exact width of the TextView in terms of ems 6446 * 6447 * @see #setWidth(int) 6448 * 6449 * @attr ref android.R.styleable#TextView_ems 6450 */ 6451 @android.view.RemotableViewMethod setEms(int ems)6452 public void setEms(int ems) { 6453 mMaxWidth = mMinWidth = ems; 6454 mMaxWidthMode = mMinWidthMode = EMS; 6455 6456 requestLayout(); 6457 invalidate(); 6458 } 6459 6460 /** 6461 * Sets the width of the TextView to be exactly {@code pixels} wide. 6462 * <p> 6463 * This value is used for width calculation if LayoutParams does not force TextView to have an 6464 * exact width. Setting this value overrides previous minimum/maximum width configurations 6465 * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}. 6466 * 6467 * @param pixels the exact width of the TextView in terms of pixels 6468 * 6469 * @see #setEms(int) 6470 * 6471 * @attr ref android.R.styleable#TextView_width 6472 */ 6473 @android.view.RemotableViewMethod setWidth(int pixels)6474 public void setWidth(int pixels) { 6475 mMaxWidth = mMinWidth = pixels; 6476 mMaxWidthMode = mMinWidthMode = PIXELS; 6477 6478 requestLayout(); 6479 invalidate(); 6480 } 6481 6482 /** 6483 * Sets line spacing for this TextView. Each line other than the last line will have its height 6484 * multiplied by {@code mult} and have {@code add} added to it. 6485 * 6486 * @param add The value in pixels that should be added to each line other than the last line. 6487 * This will be applied after the multiplier 6488 * @param mult The value by which each line height other than the last line will be multiplied 6489 * by 6490 * 6491 * @attr ref android.R.styleable#TextView_lineSpacingExtra 6492 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 6493 */ setLineSpacing(float add, float mult)6494 public void setLineSpacing(float add, float mult) { 6495 if (mSpacingAdd != add || mSpacingMult != mult) { 6496 mSpacingAdd = add; 6497 mSpacingMult = mult; 6498 6499 if (mLayout != null) { 6500 nullLayouts(); 6501 requestLayout(); 6502 invalidate(); 6503 } 6504 } 6505 } 6506 6507 /** 6508 * Gets the line spacing multiplier 6509 * 6510 * @return the value by which each line's height is multiplied to get its actual height. 6511 * 6512 * @see #setLineSpacing(float, float) 6513 * @see #getLineSpacingExtra() 6514 * 6515 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 6516 */ 6517 @InspectableProperty getLineSpacingMultiplier()6518 public float getLineSpacingMultiplier() { 6519 return mSpacingMult; 6520 } 6521 6522 /** 6523 * Gets the line spacing extra space 6524 * 6525 * @return the extra space that is added to the height of each lines of this TextView. 6526 * 6527 * @see #setLineSpacing(float, float) 6528 * @see #getLineSpacingMultiplier() 6529 * 6530 * @attr ref android.R.styleable#TextView_lineSpacingExtra 6531 */ 6532 @InspectableProperty getLineSpacingExtra()6533 public float getLineSpacingExtra() { 6534 return mSpacingAdd; 6535 } 6536 6537 /** 6538 * Sets an explicit line height for this TextView. This is equivalent to the vertical distance 6539 * between subsequent baselines in the TextView. 6540 * 6541 * @param lineHeight the line height in pixels 6542 * 6543 * @see #setLineSpacing(float, float) 6544 * @see #getLineSpacingExtra() 6545 * 6546 * @attr ref android.R.styleable#TextView_lineHeight 6547 */ 6548 @android.view.RemotableViewMethod setLineHeight(@x @ntRangefrom = 0) int lineHeight)6549 public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { 6550 setLineHeightPx(lineHeight); 6551 } 6552 setLineHeightPx(@x @loatRangefrom = 0) float lineHeight)6553 private void setLineHeightPx(@Px @FloatRange(from = 0) float lineHeight) { 6554 Preconditions.checkArgumentNonNegative(lineHeight, 6555 "Expecting non-negative lineHeight while the input is " + lineHeight); 6556 6557 final int fontHeight = getPaint().getFontMetricsInt(null); 6558 // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw. 6559 // TODO(b/274974975): should this also check if lineSpacing needs to change? 6560 if (lineHeight != fontHeight) { 6561 // Set lineSpacingExtra by the difference of lineSpacing with lineHeight 6562 setLineSpacing(lineHeight - fontHeight, 1f); 6563 6564 mLineHeightComplexDimen = 6565 TypedValue.createComplexDimension(lineHeight, TypedValue.COMPLEX_UNIT_PX); 6566 } 6567 } 6568 6569 /** 6570 * Sets an explicit line height to a given unit and value for this TextView. This is equivalent 6571 * to the vertical distance between subsequent baselines in the TextView. See {@link 6572 * TypedValue} for the possible dimension units. 6573 * 6574 * @param unit The desired dimension unit. SP units are strongly recommended so that line height 6575 * stays proportional to the text size when fonts are scaled up for accessibility. 6576 * @param lineHeight The desired line height in the given units. 6577 * 6578 * @see #setLineSpacing(float, float) 6579 * @see #getLineSpacingExtra() 6580 * 6581 * @attr ref android.R.styleable#TextView_lineHeight 6582 */ 6583 @android.view.RemotableViewMethod setLineHeight( @ypedValue.ComplexDimensionUnit int unit, @FloatRange(from = 0) float lineHeight )6584 public void setLineHeight( 6585 @TypedValue.ComplexDimensionUnit int unit, 6586 @FloatRange(from = 0) float lineHeight 6587 ) { 6588 var metrics = getDisplayMetricsOrSystem(); 6589 // We can avoid the recalculation if we know non-linear font scaling isn't being used 6590 // (an optimization for the majority case). 6591 // We also don't try to do the recalculation unless both textSize and lineHeight are in SP. 6592 if (!FontScaleConverterFactory.isNonLinearFontScalingActive( 6593 getResources().getConfiguration().fontScale) 6594 || unit != TypedValue.COMPLEX_UNIT_SP 6595 || mTextSizeUnit != TypedValue.COMPLEX_UNIT_SP 6596 ) { 6597 setLineHeightPx(TypedValue.applyDimension(unit, lineHeight, metrics)); 6598 6599 // Do this last so it overwrites what setLineHeightPx() sets it to. 6600 mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit); 6601 return; 6602 } 6603 6604 // Recalculate a proportional line height when non-linear font scaling is in effect. 6605 // Otherwise, a desired 2x line height at font scale 1.0 will not be 2x at font scale 2.0, 6606 // due to non-linear font scaling compressing higher SP sizes. See b/273326061 for details. 6607 // We know they are using SP units for both the text size and the line height 6608 // at this point, so determine the ratio between them. This is the *intended* line spacing 6609 // multiplier if font scale == 1.0. We can then determine what the pixel value for the line 6610 // height would be if we preserved proportions. 6611 var textSizePx = getTextSize(); 6612 var textSizeSp = TypedValue.convertPixelsToDimension( 6613 TypedValue.COMPLEX_UNIT_SP, 6614 textSizePx, 6615 metrics 6616 ); 6617 var ratio = lineHeight / textSizeSp; 6618 setLineHeightPx(textSizePx * ratio); 6619 6620 // Do this last so it overwrites what setLineHeightPx() sets it to. 6621 mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit); 6622 } 6623 maybeRecalculateLineHeight()6624 private void maybeRecalculateLineHeight() { 6625 if (mLineHeightComplexDimen == 0) { 6626 return; 6627 } 6628 int unit = TypedValue.getUnitFromComplexDimension(mLineHeightComplexDimen); 6629 if (unit != TypedValue.COMPLEX_UNIT_SP) { 6630 // The lineHeight was never supplied in SP, so we didn't do any fancy recalculations 6631 // in setLineHeight(). We don't need to recalculate. 6632 return; 6633 } 6634 6635 setLineHeight(unit, TypedValue.complexToFloat(mLineHeightComplexDimen)); 6636 } 6637 6638 /** 6639 * Set Highlights 6640 * 6641 * @param highlights A highlight object. Call with null for reset. 6642 * 6643 * @see #getHighlights() 6644 * @see Highlights 6645 */ setHighlights(@ullable Highlights highlights)6646 public void setHighlights(@Nullable Highlights highlights) { 6647 mHighlights = highlights; 6648 mHighlightPathsBogus = true; 6649 invalidate(); 6650 } 6651 6652 /** 6653 * Returns highlights 6654 * 6655 * @return a highlight to be drawn. null if no highlight was set. 6656 * 6657 * @see #setHighlights(Highlights) 6658 * @see Highlights 6659 * 6660 */ 6661 @Nullable getHighlights()6662 public Highlights getHighlights() { 6663 return mHighlights; 6664 } 6665 6666 /** 6667 * Sets the search result ranges with flatten range representation. 6668 * 6669 * Ranges are represented of flattened inclusive start and exclusive end integers array. The 6670 * inclusive start offset of the {@code i}-th range is stored in {@code 2 * i}-th of the array. 6671 * The exclusive end offset of the {@code i}-th range is stored in {@code 2* i + 1}-th of the 6672 * array. For example, the two ranges: (1, 2) and (3, 4) are flattened into single int array 6673 * [1, 2, 3, 4]. 6674 * 6675 * TextView will render the search result with the highlights with specified color in the theme. 6676 * If there is a focused search result, it is rendered with focused color. By calling this 6677 * method, the focused search index will be cleared. 6678 * 6679 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6680 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6681 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6682 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6683 * 6684 * @see #getSearchResultHighlights() 6685 * @see #setFocusedSearchResultIndex(int) 6686 * @see #getFocusedSearchResultIndex() 6687 * @see #setSearchResultHighlightColor(int) 6688 * @see #getSearchResultHighlightColor() 6689 * @see #setFocusedSearchResultHighlightColor(int) 6690 * @see #getFocusedSearchResultHighlightColor() 6691 * 6692 * @param ranges the flatten ranges of the search result. null for clear. 6693 */ setSearchResultHighlights(@ullable int... ranges)6694 public void setSearchResultHighlights(@Nullable int... ranges) { 6695 if (ranges == null) { 6696 mSearchResultHighlights = null; 6697 mHighlightPathsBogus = true; 6698 return; 6699 } 6700 if (ranges.length % 2 == 1) { 6701 throw new IllegalArgumentException( 6702 "Flatten ranges must have even numbered elements"); 6703 } 6704 for (int j = 0; j < ranges.length / 2; ++j) { 6705 int start = ranges[j * 2]; 6706 int end = ranges[j * 2 + 1]; 6707 if (start > end) { 6708 throw new IllegalArgumentException( 6709 "Reverse range found in the flatten range: " + start + ", " + end + "" 6710 + " at " + j + "-th range"); 6711 } 6712 } 6713 mHighlightPathsBogus = true; 6714 mSearchResultHighlights = ranges; 6715 mFocusedSearchResultIndex = FOCUSED_SEARCH_RESULT_INDEX_NONE; 6716 invalidate(); 6717 } 6718 6719 /** 6720 * Gets the current search result ranges. 6721 * 6722 * @see #setSearchResultHighlights(int[]) 6723 * @see #setFocusedSearchResultIndex(int) 6724 * @see #getFocusedSearchResultIndex() 6725 * @see #setSearchResultHighlightColor(int) 6726 * @see #getSearchResultHighlightColor() 6727 * @see #setFocusedSearchResultHighlightColor(int) 6728 * @see #getFocusedSearchResultHighlightColor() 6729 * 6730 * @return a flatten search result ranges. null if not available. 6731 */ 6732 @Nullable getSearchResultHighlights()6733 public int[] getSearchResultHighlights() { 6734 return mSearchResultHighlights; 6735 } 6736 6737 /** 6738 * A special index used for {@link #setFocusedSearchResultIndex(int)} and 6739 * {@link #getFocusedSearchResultIndex()} inidicating there is no focused search result. 6740 */ 6741 public static final int FOCUSED_SEARCH_RESULT_INDEX_NONE = -1; 6742 6743 /** 6744 * Sets the focused search result index. 6745 * 6746 * The focused search result is drawn in a focused color. 6747 * Calling {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} for clearing focused search result. 6748 * 6749 * This method must be called after setting search result ranges by 6750 * {@link #setSearchResultHighlights(int[])}. 6751 * 6752 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6753 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6754 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6755 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6756 * 6757 * @see #setSearchResultHighlights(int[]) 6758 * @see #getSearchResultHighlights() 6759 * @see #setFocusedSearchResultIndex(int) 6760 * @see #getFocusedSearchResultIndex() 6761 * @see #setSearchResultHighlightColor(int) 6762 * @see #getSearchResultHighlightColor() 6763 * @see #setFocusedSearchResultHighlightColor(int) 6764 * @see #getFocusedSearchResultHighlightColor() 6765 * 6766 * @param index a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} 6767 */ setFocusedSearchResultIndex(int index)6768 public void setFocusedSearchResultIndex(int index) { 6769 if (mSearchResultHighlights == null) { 6770 throw new IllegalArgumentException("Search result range must be set beforehand."); 6771 } 6772 if (index < -1 || index >= mSearchResultHighlights.length / 2) { 6773 throw new IllegalArgumentException("Focused index(" + index + ") must be larger than " 6774 + "-1 and less than range count(" + (mSearchResultHighlights.length / 2) + ")"); 6775 } 6776 mFocusedSearchResultIndex = index; 6777 mHighlightPathsBogus = true; 6778 invalidate(); 6779 } 6780 6781 /** 6782 * Gets the focused search result index. 6783 * 6784 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6785 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6786 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6787 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6788 * 6789 * @see #setSearchResultHighlights(int[]) 6790 * @see #getSearchResultHighlights() 6791 * @see #setFocusedSearchResultIndex(int) 6792 * @see #getFocusedSearchResultIndex() 6793 * @see #setSearchResultHighlightColor(int) 6794 * @see #getSearchResultHighlightColor() 6795 * @see #setFocusedSearchResultHighlightColor(int) 6796 * @see #getFocusedSearchResultHighlightColor() 6797 6798 * @return a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} 6799 */ getFocusedSearchResultIndex()6800 public int getFocusedSearchResultIndex() { 6801 return mFocusedSearchResultIndex; 6802 } 6803 6804 /** 6805 * Sets the search result highlight color. 6806 * 6807 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6808 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6809 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6810 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6811 * 6812 * @see #setSearchResultHighlights(int[]) 6813 * @see #getSearchResultHighlights() 6814 * @see #setFocusedSearchResultIndex(int) 6815 * @see #getFocusedSearchResultIndex() 6816 * @see #setSearchResultHighlightColor(int) 6817 * @see #getSearchResultHighlightColor() 6818 * @see #setFocusedSearchResultHighlightColor(int) 6819 * @see #getFocusedSearchResultHighlightColor() 6820 6821 * @param color a search result highlight color. 6822 */ setSearchResultHighlightColor(@olorInt int color)6823 public void setSearchResultHighlightColor(@ColorInt int color) { 6824 mSearchResultHighlightColor = color; 6825 } 6826 6827 /** 6828 * Gets the search result highlight color. 6829 * 6830 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6831 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6832 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6833 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6834 * 6835 * @see #setSearchResultHighlights(int[]) 6836 * @see #getSearchResultHighlights() 6837 * @see #setFocusedSearchResultIndex(int) 6838 * @see #getFocusedSearchResultIndex() 6839 * @see #setSearchResultHighlightColor(int) 6840 * @see #getSearchResultHighlightColor() 6841 * @see #setFocusedSearchResultHighlightColor(int) 6842 * @see #getFocusedSearchResultHighlightColor() 6843 6844 * @return a search result highlight color. 6845 */ 6846 @ColorInt getSearchResultHighlightColor()6847 public int getSearchResultHighlightColor() { 6848 return mSearchResultHighlightColor; 6849 } 6850 6851 /** 6852 * Sets focused search result highlight color. 6853 * 6854 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6855 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6856 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6857 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6858 * 6859 * @see #setSearchResultHighlights(int[]) 6860 * @see #getSearchResultHighlights() 6861 * @see #setFocusedSearchResultIndex(int) 6862 * @see #getFocusedSearchResultIndex() 6863 * @see #setSearchResultHighlightColor(int) 6864 * @see #getSearchResultHighlightColor() 6865 * @see #setFocusedSearchResultHighlightColor(int) 6866 * @see #getFocusedSearchResultHighlightColor() 6867 6868 * @param color a focused search result highlight color. 6869 */ setFocusedSearchResultHighlightColor(@olorInt int color)6870 public void setFocusedSearchResultHighlightColor(@ColorInt int color) { 6871 mFocusedSearchResultHighlightColor = color; 6872 } 6873 6874 /** 6875 * Gets focused search result highlight color. 6876 * 6877 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6878 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6879 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6880 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6881 * 6882 * @see #setSearchResultHighlights(int[]) 6883 * @see #getSearchResultHighlights() 6884 * @see #setFocusedSearchResultIndex(int) 6885 * @see #getFocusedSearchResultIndex() 6886 * @see #setSearchResultHighlightColor(int) 6887 * @see #getSearchResultHighlightColor() 6888 * @see #setFocusedSearchResultHighlightColor(int) 6889 * @see #getFocusedSearchResultHighlightColor() 6890 6891 * @return a focused search result highlight color. 6892 */ 6893 @ColorInt getFocusedSearchResultHighlightColor()6894 public int getFocusedSearchResultHighlightColor() { 6895 return mFocusedSearchResultHighlightColor; 6896 } 6897 6898 /** 6899 * Highlights the text range (from inclusive start offset to exclusive end offset) to show what 6900 * will be selected by the ongoing select handwriting gesture. While the gesture preview 6901 * highlight is shown, the selection or cursor is hidden. If the text or selection is changed, 6902 * the gesture preview highlight will be cleared. 6903 */ setSelectGesturePreviewHighlight(int start, int end)6904 private void setSelectGesturePreviewHighlight(int start, int end) { 6905 // Selection preview highlight color is the same as selection highlight color. 6906 setGesturePreviewHighlight(start, end, mHighlightColor); 6907 } 6908 6909 /** 6910 * Highlights the text range (from inclusive start offset to exclusive end offset) to show what 6911 * will be deleted by the ongoing delete handwriting gesture. While the gesture preview 6912 * highlight is shown, the selection or cursor is hidden. If the text or selection is changed, 6913 * the gesture preview highlight will be cleared. 6914 */ setDeleteGesturePreviewHighlight(int start, int end)6915 private void setDeleteGesturePreviewHighlight(int start, int end) { 6916 // Deletion preview highlight color is 20% opacity of the default text color. 6917 int color = mTextColor.getDefaultColor(); 6918 color = ColorUtils.setAlphaComponent(color, (int) (0.2f * Color.alpha(color))); 6919 setGesturePreviewHighlight(start, end, color); 6920 } 6921 setGesturePreviewHighlight(int start, int end, int color)6922 private void setGesturePreviewHighlight(int start, int end, int color) { 6923 mGesturePreviewHighlightStart = start; 6924 mGesturePreviewHighlightEnd = end; 6925 if (mGesturePreviewHighlightPaint == null) { 6926 mGesturePreviewHighlightPaint = new Paint(); 6927 mGesturePreviewHighlightPaint.setStyle(Paint.Style.FILL); 6928 } 6929 mGesturePreviewHighlightPaint.setColor(color); 6930 6931 if (mEditor != null) { 6932 mEditor.hideCursorAndSpanControllers(); 6933 mEditor.stopTextActionModeWithPreservingSelection(); 6934 } 6935 6936 mHighlightPathsBogus = true; 6937 invalidate(); 6938 } 6939 clearGesturePreviewHighlight()6940 private void clearGesturePreviewHighlight() { 6941 mGesturePreviewHighlightStart = -1; 6942 mGesturePreviewHighlightEnd = -1; 6943 mHighlightPathsBogus = true; 6944 invalidate(); 6945 } 6946 hasGesturePreviewHighlight()6947 boolean hasGesturePreviewHighlight() { 6948 return mGesturePreviewHighlightStart >= 0; 6949 } 6950 6951 /** 6952 * Convenience method to append the specified text to the TextView's 6953 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 6954 * if it was not already editable. 6955 * 6956 * @param text text to be appended to the already displayed text 6957 */ append(CharSequence text)6958 public final void append(CharSequence text) { 6959 append(text, 0, text.length()); 6960 } 6961 6962 /** 6963 * Convenience method to append the specified text slice to the TextView's 6964 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 6965 * if it was not already editable. 6966 * 6967 * @param text text to be appended to the already displayed text 6968 * @param start the index of the first character in the {@code text} 6969 * @param end the index of the character following the last character in the {@code text} 6970 * 6971 * @see Appendable#append(CharSequence, int, int) 6972 */ append(CharSequence text, int start, int end)6973 public void append(CharSequence text, int start, int end) { 6974 if (!(mText instanceof Editable)) { 6975 setText(mText, BufferType.EDITABLE); 6976 } 6977 6978 ((Editable) mText).append(text, start, end); 6979 6980 if (mAutoLinkMask != 0) { 6981 boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask); 6982 // Do not change the movement method for text that support text selection as it 6983 // would prevent an arbitrary cursor displacement. 6984 if (linksWereAdded && mLinksClickable && !textCanBeSelected()) { 6985 setMovementMethod(LinkMovementMethod.getInstance()); 6986 } 6987 } 6988 } 6989 updateTextColors()6990 private void updateTextColors() { 6991 boolean inval = false; 6992 final int[] drawableState = getDrawableState(); 6993 int color = mTextColor.getColorForState(drawableState, 0); 6994 if (color != mCurTextColor) { 6995 mCurTextColor = color; 6996 inval = true; 6997 } 6998 if (mLinkTextColor != null) { 6999 color = mLinkTextColor.getColorForState(drawableState, 0); 7000 if (color != mTextPaint.linkColor) { 7001 mTextPaint.linkColor = color; 7002 inval = true; 7003 } 7004 } 7005 if (mHintTextColor != null) { 7006 color = mHintTextColor.getColorForState(drawableState, 0); 7007 if (color != mCurHintTextColor) { 7008 mCurHintTextColor = color; 7009 if (mText.length() == 0) { 7010 inval = true; 7011 } 7012 } 7013 } 7014 if (inval) { 7015 // Text needs to be redrawn with the new color 7016 if (mEditor != null) mEditor.invalidateTextDisplayList(); 7017 invalidate(); 7018 } 7019 } 7020 7021 @Override drawableStateChanged()7022 protected void drawableStateChanged() { 7023 super.drawableStateChanged(); 7024 7025 if (mTextColor != null && mTextColor.isStateful() 7026 || (mHintTextColor != null && mHintTextColor.isStateful()) 7027 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 7028 updateTextColors(); 7029 } 7030 7031 if (mDrawables != null) { 7032 final int[] state = getDrawableState(); 7033 for (Drawable dr : mDrawables.mShowing) { 7034 if (dr != null && dr.isStateful() && dr.setState(state)) { 7035 invalidateDrawable(dr); 7036 } 7037 } 7038 } 7039 } 7040 7041 @Override drawableHotspotChanged(float x, float y)7042 public void drawableHotspotChanged(float x, float y) { 7043 super.drawableHotspotChanged(x, y); 7044 7045 if (mDrawables != null) { 7046 for (Drawable dr : mDrawables.mShowing) { 7047 if (dr != null) { 7048 dr.setHotspot(x, y); 7049 } 7050 } 7051 } 7052 } 7053 7054 @Override onSaveInstanceState()7055 public Parcelable onSaveInstanceState() { 7056 Parcelable superState = super.onSaveInstanceState(); 7057 7058 // Save state if we are forced to 7059 final boolean freezesText = getFreezesText(); 7060 boolean hasSelection = false; 7061 int start = -1; 7062 int end = -1; 7063 7064 if (mText != null) { 7065 start = getSelectionStart(); 7066 end = getSelectionEnd(); 7067 if (start >= 0 || end >= 0) { 7068 // Or save state if there is a selection 7069 hasSelection = true; 7070 } 7071 } 7072 7073 if (freezesText || hasSelection) { 7074 SavedState ss = new SavedState(superState); 7075 7076 if (freezesText) { 7077 if (mText instanceof Spanned) { 7078 final Spannable sp = new SpannableStringBuilder(mText); 7079 7080 if (mEditor != null) { 7081 removeMisspelledSpans(sp); 7082 sp.removeSpan(mEditor.mSuggestionRangeSpan); 7083 } 7084 7085 ss.text = sp; 7086 } else { 7087 ss.text = mText.toString(); 7088 } 7089 } 7090 7091 if (hasSelection) { 7092 // XXX Should also save the current scroll position! 7093 ss.selStart = start; 7094 ss.selEnd = end; 7095 } 7096 7097 if (isFocused() && start >= 0 && end >= 0) { 7098 ss.frozenWithFocus = true; 7099 } 7100 7101 ss.error = getError(); 7102 7103 if (mEditor != null) { 7104 ss.editorState = mEditor.saveInstanceState(); 7105 } 7106 return ss; 7107 } 7108 7109 return superState; 7110 } 7111 removeMisspelledSpans(Spannable spannable)7112 void removeMisspelledSpans(Spannable spannable) { 7113 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), 7114 SuggestionSpan.class); 7115 for (int i = 0; i < suggestionSpans.length; i++) { 7116 int flags = suggestionSpans[i].getFlags(); 7117 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 7118 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { 7119 spannable.removeSpan(suggestionSpans[i]); 7120 } 7121 } 7122 } 7123 7124 @Override onRestoreInstanceState(Parcelable state)7125 public void onRestoreInstanceState(Parcelable state) { 7126 if (!(state instanceof SavedState)) { 7127 super.onRestoreInstanceState(state); 7128 return; 7129 } 7130 7131 SavedState ss = (SavedState) state; 7132 super.onRestoreInstanceState(ss.getSuperState()); 7133 7134 // XXX restore buffer type too, as well as lots of other stuff 7135 if (ss.text != null) { 7136 setText(ss.text); 7137 } 7138 7139 if (ss.selStart >= 0 && ss.selEnd >= 0) { 7140 if (mSpannable != null) { 7141 int len = mText.length(); 7142 7143 if (ss.selStart > len || ss.selEnd > len) { 7144 String restored = ""; 7145 7146 if (ss.text != null) { 7147 restored = "(restored) "; 7148 } 7149 7150 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd 7151 + " out of range for " + restored + "text " + mText); 7152 } else { 7153 Selection.setSelection(mSpannable, ss.selStart, ss.selEnd); 7154 7155 if (ss.frozenWithFocus) { 7156 createEditorIfNeeded(); 7157 mEditor.mFrozenWithFocus = true; 7158 } 7159 } 7160 } 7161 } 7162 7163 if (ss.error != null) { 7164 final CharSequence error = ss.error; 7165 // Display the error later, after the first layout pass 7166 post(new Runnable() { 7167 public void run() { 7168 if (mEditor == null || !mEditor.mErrorWasChanged) { 7169 setError(error); 7170 } 7171 } 7172 }); 7173 } 7174 7175 if (ss.editorState != null) { 7176 createEditorIfNeeded(); 7177 mEditor.restoreInstanceState(ss.editorState); 7178 } 7179 } 7180 7181 /** 7182 * Control whether this text view saves its entire text contents when 7183 * freezing to an icicle, in addition to dynamic state such as cursor 7184 * position. By default this is false, not saving the text. Set to true 7185 * if the text in the text view is not being saved somewhere else in 7186 * persistent storage (such as in a content provider) so that if the 7187 * view is later thawed the user will not lose their data. For 7188 * {@link android.widget.EditText} it is always enabled, regardless of 7189 * the value of the attribute. 7190 * 7191 * @param freezesText Controls whether a frozen icicle should include the 7192 * entire text data: true to include it, false to not. 7193 * 7194 * @attr ref android.R.styleable#TextView_freezesText 7195 */ 7196 @android.view.RemotableViewMethod setFreezesText(boolean freezesText)7197 public void setFreezesText(boolean freezesText) { 7198 mFreezesText = freezesText; 7199 } 7200 7201 /** 7202 * Return whether this text view is including its entire text contents 7203 * in frozen icicles. For {@link android.widget.EditText} it always returns true. 7204 * 7205 * @return Returns true if text is included, false if it isn't. 7206 * 7207 * @see #setFreezesText 7208 */ 7209 @InspectableProperty getFreezesText()7210 public boolean getFreezesText() { 7211 return mFreezesText; 7212 } 7213 7214 /////////////////////////////////////////////////////////////////////////// 7215 7216 /** 7217 * Sets the Factory used to create new {@link Editable Editables}. 7218 * 7219 * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used 7220 * 7221 * @see android.text.Editable.Factory 7222 * @see android.widget.TextView.BufferType#EDITABLE 7223 */ setEditableFactory(Editable.Factory factory)7224 public final void setEditableFactory(Editable.Factory factory) { 7225 mEditableFactory = factory; 7226 setText(mText); 7227 } 7228 7229 /** 7230 * Sets the Factory used to create new {@link Spannable Spannables}. 7231 * 7232 * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used 7233 * 7234 * @see android.text.Spannable.Factory 7235 * @see android.widget.TextView.BufferType#SPANNABLE 7236 */ setSpannableFactory(Spannable.Factory factory)7237 public final void setSpannableFactory(Spannable.Factory factory) { 7238 mSpannableFactory = factory; 7239 setText(mText); 7240 } 7241 7242 /** 7243 * Sets the text to be displayed. TextView <em>does not</em> accept 7244 * HTML-like formatting, which you can do with text strings in XML resource files. 7245 * To style your strings, attach android.text.style.* objects to a 7246 * {@link android.text.SpannableString}, or see the 7247 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 7248 * Available Resource Types</a> documentation for an example of setting 7249 * formatted text in the XML resource file. 7250 * <p/> 7251 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 7252 * intermediate {@link Spannable Spannables}. Likewise it will use 7253 * {@link android.text.Editable.Factory} to create final or intermediate 7254 * {@link Editable Editables}. 7255 * 7256 * If the passed text is a {@link PrecomputedText} but the parameters used to create the 7257 * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure 7258 * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this. 7259 * 7260 * @param text text to be displayed 7261 * 7262 * @attr ref android.R.styleable#TextView_text 7263 * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the 7264 * parameters used to create the PrecomputedText mismatches 7265 * with this TextView. 7266 */ 7267 @android.view.RemotableViewMethod(asyncImpl = "setTextAsync") setText(CharSequence text)7268 public final void setText(CharSequence text) { 7269 setText(text, mBufferType); 7270 } 7271 7272 /** 7273 * RemotableViewMethod's asyncImpl of {@link #setText(CharSequence)}. 7274 * This should be called on a background thread, and returns a Runnable which is then must be 7275 * called on the main thread to complete the operation and set text. 7276 * @param text text to be displayed 7277 * @return Runnable that sets text; must be called on the main thread by the caller of this 7278 * method to complete the operation 7279 * @hide 7280 */ 7281 @NonNull setTextAsync(@ullable CharSequence text)7282 public Runnable setTextAsync(@Nullable CharSequence text) { 7283 return () -> setText(text); 7284 } 7285 7286 /** 7287 * Sets the text to be displayed but retains the cursor position. Same as 7288 * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the 7289 * new text. 7290 * <p/> 7291 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 7292 * intermediate {@link Spannable Spannables}. Likewise it will use 7293 * {@link android.text.Editable.Factory} to create final or intermediate 7294 * {@link Editable Editables}. 7295 * 7296 * @param text text to be displayed 7297 * 7298 * @see #setText(CharSequence) 7299 */ 7300 @android.view.RemotableViewMethod setTextKeepState(CharSequence text)7301 public final void setTextKeepState(CharSequence text) { 7302 setTextKeepState(text, mBufferType); 7303 } 7304 7305 /** 7306 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}. 7307 * <p/> 7308 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 7309 * intermediate {@link Spannable Spannables}. Likewise it will use 7310 * {@link android.text.Editable.Factory} to create final or intermediate 7311 * {@link Editable Editables}. 7312 * 7313 * Subclasses overriding this method should ensure that the following post condition holds, 7314 * in order to guarantee the safety of the view's measurement and layout operations: 7315 * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed} 7316 * will be different from {@code null}. 7317 * 7318 * @param text text to be displayed 7319 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 7320 * stored as a static text, styleable/spannable text, or editable text 7321 * 7322 * @see #setText(CharSequence) 7323 * @see android.widget.TextView.BufferType 7324 * @see #setSpannableFactory(Spannable.Factory) 7325 * @see #setEditableFactory(Editable.Factory) 7326 * 7327 * @attr ref android.R.styleable#TextView_text 7328 * @attr ref android.R.styleable#TextView_bufferType 7329 */ setText(CharSequence text, BufferType type)7330 public void setText(CharSequence text, BufferType type) { 7331 setText(text, type, true, 0); 7332 7333 // drop any potential mCharWrappper leaks 7334 mCharWrapper = null; 7335 } 7336 7337 @UnsupportedAppUsage setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)7338 private void setText(CharSequence text, BufferType type, 7339 boolean notifyBefore, int oldlen) { 7340 if (mEditor != null) { 7341 mEditor.beforeSetText(); 7342 } 7343 mTextSetFromXmlOrResourceId = false; 7344 if (text == null) { 7345 text = ""; 7346 } 7347 7348 // If suggestions are not enabled, remove the suggestion spans from the text 7349 if (!isSuggestionsEnabled()) { 7350 text = removeSuggestionSpans(text); 7351 } 7352 7353 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 7354 7355 if (text instanceof Spanned 7356 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 7357 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { 7358 setHorizontalFadingEdgeEnabled(true); 7359 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 7360 } else { 7361 setHorizontalFadingEdgeEnabled(false); 7362 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 7363 } 7364 setEllipsize(TextUtils.TruncateAt.MARQUEE); 7365 } 7366 7367 int n = mFilters.length; 7368 for (int i = 0; i < n; i++) { 7369 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); 7370 if (out != null) { 7371 text = out; 7372 } 7373 } 7374 7375 if (notifyBefore) { 7376 if (mText != null) { 7377 oldlen = mText.length(); 7378 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 7379 } else { 7380 sendBeforeTextChanged("", 0, 0, text.length()); 7381 } 7382 } 7383 7384 boolean needEditableForNotification = false; 7385 7386 if (mListeners != null && mListeners.size() != 0) { 7387 needEditableForNotification = true; 7388 } 7389 7390 PrecomputedText precomputed = 7391 (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 7392 if (type == BufferType.EDITABLE || getKeyListener() != null 7393 || needEditableForNotification) { 7394 createEditorIfNeeded(); 7395 mEditor.forgetUndoRedo(); 7396 mEditor.scheduleRestartInputForSetText(); 7397 Editable t = mEditableFactory.newEditable(text); 7398 text = t; 7399 setFilters(t, mFilters); 7400 } else if (precomputed != null) { 7401 if (mTextDir == null) { 7402 mTextDir = getTextDirectionHeuristic(); 7403 } 7404 final @PrecomputedText.Params.CheckResultUsableResult int checkResult = 7405 precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, 7406 mHyphenationFrequency, LineBreakConfig.getLineBreakConfig( 7407 mLineBreakStyle, mLineBreakWordStyle)); 7408 switch (checkResult) { 7409 case PrecomputedText.Params.UNUSABLE: 7410 throw new IllegalArgumentException( 7411 "PrecomputedText's Parameters don't match the parameters of this TextView." 7412 + "Consider using setTextMetricsParams(precomputedText.getParams()) " 7413 + "to override the settings of this TextView: " 7414 + "PrecomputedText: " + precomputed.getParams() 7415 + "TextView: " + getTextMetricsParams()); 7416 case PrecomputedText.Params.NEED_RECOMPUTE: 7417 precomputed = PrecomputedText.create(precomputed, getTextMetricsParams()); 7418 break; 7419 case PrecomputedText.Params.USABLE: 7420 // pass through 7421 } 7422 } else if (type == BufferType.SPANNABLE || mMovement != null) { 7423 text = mSpannableFactory.newSpannable(text); 7424 } else if (!(text instanceof CharWrapper)) { 7425 text = TextUtils.stringOrSpannedString(text); 7426 } 7427 7428 @AccessibilityUtils.A11yTextChangeType int a11yTextChangeType = AccessibilityUtils.NONE; 7429 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 7430 a11yTextChangeType = AccessibilityUtils.textOrSpanChanged(text, mText); 7431 } 7432 7433 if (mAutoLinkMask != 0) { 7434 Spannable s2; 7435 7436 if (type == BufferType.EDITABLE || text instanceof Spannable) { 7437 s2 = (Spannable) text; 7438 } else { 7439 s2 = mSpannableFactory.newSpannable(text); 7440 } 7441 7442 if (Linkify.addLinks(s2, mAutoLinkMask)) { 7443 text = s2; 7444 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 7445 7446 /* 7447 * We must go ahead and set the text before changing the 7448 * movement method, because setMovementMethod() may call 7449 * setText() again to try to upgrade the buffer type. 7450 */ 7451 setTextInternal(text); 7452 if (a11yTextChangeType == AccessibilityUtils.NONE) { 7453 a11yTextChangeType = AccessibilityUtils.PARCELABLE_SPAN; 7454 } 7455 7456 // Do not change the movement method for text that support text selection as it 7457 // would prevent an arbitrary cursor displacement. 7458 if (mLinksClickable && !textCanBeSelected()) { 7459 setMovementMethod(LinkMovementMethod.getInstance()); 7460 } 7461 } 7462 } 7463 7464 mBufferType = type; 7465 setTextInternal(text); 7466 7467 if (mTransformation == null) { 7468 mTransformed = text; 7469 } else { 7470 mTransformed = mTransformation.getTransformation(text, this); 7471 } 7472 if (mTransformed == null) { 7473 // Should not happen if the transformation method follows the non-null postcondition. 7474 mTransformed = ""; 7475 } 7476 7477 final int textLength = text.length(); 7478 final boolean isOffsetMapping = mTransformed instanceof OffsetMapping; 7479 7480 if (text instanceof Spannable && (!mAllowTransformationLengthChange || isOffsetMapping)) { 7481 Spannable sp = (Spannable) text; 7482 7483 // Remove any ChangeWatchers that might have come from other TextViews. 7484 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 7485 final int count = watchers.length; 7486 for (int i = 0; i < count; i++) { 7487 sp.removeSpan(watchers[i]); 7488 } 7489 7490 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); 7491 7492 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE 7493 | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 7494 7495 if (mEditor != null) mEditor.addSpanWatchers(sp); 7496 7497 if (mTransformation != null) { 7498 final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0; 7499 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE 7500 | (priority << Spanned.SPAN_PRIORITY_SHIFT)); 7501 } 7502 7503 if (mMovement != null) { 7504 mMovement.initialize(this, (Spannable) text); 7505 7506 /* 7507 * Initializing the movement method will have set the 7508 * selection, so reset mSelectionMoved to keep that from 7509 * interfering with the normal on-focus selection-setting. 7510 */ 7511 if (mEditor != null) mEditor.mSelectionMoved = false; 7512 } 7513 } 7514 7515 if (mLayout != null) { 7516 checkForRelayout(); 7517 } 7518 7519 sendOnTextChanged(text, 0, oldlen, textLength); 7520 onTextChanged(text, 0, oldlen, textLength); 7521 7522 mHideHint = false; 7523 7524 if (a11yTextChangeType == AccessibilityUtils.TEXT) { 7525 notifyViewAccessibilityStateChangedIfNeeded( 7526 AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); 7527 } else if (a11yTextChangeType == AccessibilityUtils.PARCELABLE_SPAN) { 7528 notifyViewAccessibilityStateChangedIfNeeded( 7529 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 7530 } 7531 7532 if (needEditableForNotification) { 7533 sendAfterTextChanged((Editable) text); 7534 } else { 7535 notifyListeningManagersAfterTextChanged(); 7536 } 7537 7538 if (mEditor != null) { 7539 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 7540 mEditor.prepareCursorControllers(); 7541 7542 mEditor.maybeFireScheduledRestartInputForSetText(); 7543 } 7544 } 7545 7546 /** 7547 * Sets the TextView to display the specified slice of the specified 7548 * char array. You must promise that you will not change the contents 7549 * of the array except for right before another call to setText(), 7550 * since the TextView has no way to know that the text 7551 * has changed and that it needs to invalidate and re-layout. 7552 * 7553 * @throws NullPointerException if text is null 7554 * @throws IndexOutOfBoundsException if start or start+len are not in 0 to text.length 7555 * 7556 * @param text char array to be displayed 7557 * @param start start index in the char array 7558 * @param len length of char count after {@code start} 7559 */ setText(@onNull char[] text, int start, int len)7560 public final void setText(@NonNull char[] text, int start, int len) { 7561 int oldlen = 0; 7562 7563 if (start < 0 || len < 0 || start + len > text.length) { 7564 throw new IndexOutOfBoundsException(start + ", " + len); 7565 } 7566 7567 /* 7568 * We must do the before-notification here ourselves because if 7569 * the old text is a CharWrapper we destroy it before calling 7570 * into the normal path. 7571 */ 7572 if (mText != null) { 7573 oldlen = mText.length(); 7574 sendBeforeTextChanged(mText, 0, oldlen, len); 7575 } else { 7576 sendBeforeTextChanged("", 0, 0, len); 7577 } 7578 7579 if (mCharWrapper == null) { 7580 mCharWrapper = new CharWrapper(text, start, len); 7581 } else { 7582 mCharWrapper.set(text, start, len); 7583 } 7584 7585 setText(mCharWrapper, mBufferType, false, oldlen); 7586 } 7587 7588 /** 7589 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains 7590 * the cursor position. Same as 7591 * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor 7592 * position (if any) is retained in the new text. 7593 * <p/> 7594 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 7595 * intermediate {@link Spannable Spannables}. Likewise it will use 7596 * {@link android.text.Editable.Factory} to create final or intermediate 7597 * {@link Editable Editables}. 7598 * 7599 * @param text text to be displayed 7600 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 7601 * stored as a static text, styleable/spannable text, or editable text 7602 * 7603 * @see #setText(CharSequence, android.widget.TextView.BufferType) 7604 */ setTextKeepState(CharSequence text, BufferType type)7605 public final void setTextKeepState(CharSequence text, BufferType type) { 7606 int start = getSelectionStart(); 7607 int end = getSelectionEnd(); 7608 int len = text.length(); 7609 7610 setText(text, type); 7611 7612 if (start >= 0 || end >= 0) { 7613 if (mSpannable != null) { 7614 Selection.setSelection(mSpannable, 7615 Math.max(0, Math.min(start, len)), 7616 Math.max(0, Math.min(end, len))); 7617 } 7618 } 7619 } 7620 7621 /** 7622 * Sets the text to be displayed using a string resource identifier. 7623 * 7624 * @param resid the resource identifier of the string resource to be displayed 7625 * 7626 * @see #setText(CharSequence) 7627 * 7628 * @attr ref android.R.styleable#TextView_text 7629 */ 7630 @android.view.RemotableViewMethod setText(@tringRes int resid)7631 public final void setText(@StringRes int resid) { 7632 setText(getContext().getResources().getText(resid)); 7633 mTextSetFromXmlOrResourceId = true; 7634 mTextId = resid; 7635 } 7636 7637 /** 7638 * Sets the text to be displayed using a string resource identifier and the 7639 * {@link android.widget.TextView.BufferType}. 7640 * <p/> 7641 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 7642 * intermediate {@link Spannable Spannables}. Likewise it will use 7643 * {@link android.text.Editable.Factory} to create final or intermediate 7644 * {@link Editable Editables}. 7645 * 7646 * @param resid the resource identifier of the string resource to be displayed 7647 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 7648 * stored as a static text, styleable/spannable text, or editable text 7649 * 7650 * @see #setText(int) 7651 * @see #setText(CharSequence) 7652 * @see android.widget.TextView.BufferType 7653 * @see #setSpannableFactory(Spannable.Factory) 7654 * @see #setEditableFactory(Editable.Factory) 7655 * 7656 * @attr ref android.R.styleable#TextView_text 7657 * @attr ref android.R.styleable#TextView_bufferType 7658 */ setText(@tringRes int resid, BufferType type)7659 public final void setText(@StringRes int resid, BufferType type) { 7660 setText(getContext().getResources().getText(resid), type); 7661 mTextSetFromXmlOrResourceId = true; 7662 mTextId = resid; 7663 } 7664 7665 /** 7666 * Sets the text to be displayed when the text of the TextView is empty. 7667 * Null means to use the normal empty text. The hint does not currently 7668 * participate in determining the size of the view. 7669 * 7670 * @attr ref android.R.styleable#TextView_hint 7671 */ 7672 @android.view.RemotableViewMethod setHint(CharSequence hint)7673 public final void setHint(CharSequence hint) { 7674 setHintInternal(hint); 7675 7676 if (mEditor != null && isInputMethodTarget()) { 7677 mEditor.reportExtractedText(); 7678 } 7679 } 7680 setHintInternal(CharSequence hint)7681 private void setHintInternal(CharSequence hint) { 7682 mHideHint = false; 7683 mHint = TextUtils.stringOrSpannedString(hint); 7684 7685 if (mLayout != null) { 7686 checkForRelayout(); 7687 } 7688 7689 if (mText.length() == 0) { 7690 invalidate(); 7691 } 7692 7693 // Invalidate display list if hint is currently used 7694 if (mEditor != null && mText.length() == 0 && mHint != null) { 7695 mEditor.invalidateTextDisplayList(); 7696 } 7697 } 7698 7699 /** 7700 * Sets the text to be displayed when the text of the TextView is empty, 7701 * from a resource. 7702 * 7703 * @attr ref android.R.styleable#TextView_hint 7704 */ 7705 @android.view.RemotableViewMethod setHint(@tringRes int resid)7706 public final void setHint(@StringRes int resid) { 7707 mHintId = resid; 7708 setHint(getContext().getResources().getText(resid)); 7709 } 7710 7711 /** 7712 * Returns the hint that is displayed when the text of the TextView 7713 * is empty. 7714 * 7715 * @attr ref android.R.styleable#TextView_hint 7716 */ 7717 @InspectableProperty 7718 @ViewDebug.CapturedViewProperty getHint()7719 public CharSequence getHint() { 7720 return mHint; 7721 } 7722 7723 /** 7724 * Temporarily hides the hint text until the text is modified, or the hint text is modified, or 7725 * the view gains or loses focus. 7726 * 7727 * @hide 7728 */ hideHint()7729 public void hideHint() { 7730 if (isShowingHint()) { 7731 mHideHint = true; 7732 invalidate(); 7733 } 7734 } 7735 7736 /** 7737 * Returns if the text is constrained to a single horizontally scrolling line ignoring new 7738 * line characters instead of letting it wrap onto multiple lines. 7739 * 7740 * @attr ref android.R.styleable#TextView_singleLine 7741 */ 7742 @InspectableProperty isSingleLine()7743 public boolean isSingleLine() { 7744 return mSingleLine; 7745 } 7746 isMultilineInputType(int type)7747 private static boolean isMultilineInputType(int type) { 7748 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) 7749 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 7750 } 7751 7752 /** 7753 * Removes the suggestion spans. 7754 */ removeSuggestionSpans(CharSequence text)7755 CharSequence removeSuggestionSpans(CharSequence text) { 7756 if (text instanceof Spanned) { 7757 Spannable spannable; 7758 if (text instanceof Spannable) { 7759 spannable = (Spannable) text; 7760 } else { 7761 spannable = mSpannableFactory.newSpannable(text); 7762 } 7763 7764 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 7765 if (spans.length == 0) { 7766 return text; 7767 } else { 7768 text = spannable; 7769 } 7770 7771 for (int i = 0; i < spans.length; i++) { 7772 spannable.removeSpan(spans[i]); 7773 } 7774 } 7775 return text; 7776 } 7777 7778 /** 7779 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 7780 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 7781 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 7782 * then a soft keyboard will not be displayed for this text view. 7783 * 7784 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 7785 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 7786 * type. 7787 * 7788 * @see #getInputType() 7789 * @see #setRawInputType(int) 7790 * @see android.text.InputType 7791 * @attr ref android.R.styleable#TextView_inputType 7792 */ setInputType(int type)7793 public void setInputType(int type) { 7794 final boolean wasPassword = isPasswordInputType(getInputType()); 7795 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 7796 setInputType(type, false); 7797 final boolean isPassword = isPasswordInputType(type); 7798 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 7799 boolean forceUpdate = false; 7800 if (isPassword) { 7801 setTransformationMethod(PasswordTransformationMethod.getInstance()); 7802 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 7803 Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED); 7804 } else if (isVisiblePassword) { 7805 if (mTransformation == PasswordTransformationMethod.getInstance()) { 7806 forceUpdate = true; 7807 } 7808 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 7809 Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED); 7810 } else if (wasPassword || wasVisiblePassword) { 7811 // not in password mode, clean up typeface and transformation 7812 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, 7813 DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL, 7814 FontStyle.FONT_WEIGHT_UNSPECIFIED); 7815 if (mTransformation == PasswordTransformationMethod.getInstance()) { 7816 forceUpdate = true; 7817 } 7818 } 7819 7820 boolean singleLine = !isMultilineInputType(type); 7821 7822 // We need to update the single line mode if it has changed or we 7823 // were previously in password mode. 7824 if (mSingleLine != singleLine || forceUpdate) { 7825 // Change single line mode, but only change the transformation if 7826 // we are not in password mode. 7827 applySingleLine(singleLine, !isPassword, true, true); 7828 } 7829 7830 if (!isSuggestionsEnabled()) { 7831 setTextInternal(removeSuggestionSpans(mText)); 7832 } 7833 7834 InputMethodManager imm = getInputMethodManager(); 7835 if (imm != null) imm.restartInput(this); 7836 } 7837 7838 /** 7839 * It would be better to rely on the input type for everything. A password inputType should have 7840 * a password transformation. We should hence use isPasswordInputType instead of this method. 7841 * 7842 * We should: 7843 * - Call setInputType in setKeyListener instead of changing the input type directly (which 7844 * would install the correct transformation). 7845 * - Refuse the installation of a non-password transformation in setTransformation if the input 7846 * type is password. 7847 * 7848 * However, this is like this for legacy reasons and we cannot break existing apps. This method 7849 * is useful since it matches what the user can see (obfuscated text or not). 7850 * 7851 * @return true if the current transformation method is of the password type. 7852 */ hasPasswordTransformationMethod()7853 boolean hasPasswordTransformationMethod() { 7854 return mTransformation instanceof PasswordTransformationMethod; 7855 } 7856 7857 /** 7858 * Returns true if the current inputType is any type of password. 7859 * 7860 * @hide 7861 */ isAnyPasswordInputType()7862 public boolean isAnyPasswordInputType() { 7863 final int inputType = getInputType(); 7864 return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType); 7865 } 7866 isPasswordInputType(int inputType)7867 static boolean isPasswordInputType(int inputType) { 7868 final int variation = 7869 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 7870 return variation 7871 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 7872 || variation 7873 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 7874 || variation 7875 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 7876 } 7877 isVisiblePasswordInputType(int inputType)7878 private static boolean isVisiblePasswordInputType(int inputType) { 7879 final int variation = 7880 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 7881 return variation 7882 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 7883 } 7884 7885 /** 7886 * Directly change the content type integer of the text view, without 7887 * modifying any other state. 7888 * @see #setInputType(int) 7889 * @see android.text.InputType 7890 * @attr ref android.R.styleable#TextView_inputType 7891 */ setRawInputType(int type)7892 public void setRawInputType(int type) { 7893 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 7894 createEditorIfNeeded(); 7895 mEditor.mInputType = type; 7896 ensureEditorFocusedNotifiedToHandwritingInitiator(); 7897 } 7898 ensureEditorFocusedNotifiedToHandwritingInitiator()7899 private void ensureEditorFocusedNotifiedToHandwritingInitiator() { 7900 if (!initiationWithoutInputConnection() || isHandwritingDelegate()) { 7901 return; 7902 } 7903 ViewRootImpl viewRoot = getViewRootImpl(); 7904 if (viewRoot == null) { 7905 return; 7906 } 7907 if (isFocused() && hasWindowFocus() && onCheckIsTextEditor()) { 7908 viewRoot.getHandwritingInitiator().onEditorFocused(this); 7909 } 7910 } 7911 7912 @Override getAutofillHints()7913 public String[] getAutofillHints() { 7914 String[] hints = super.getAutofillHints(); 7915 if (isAnyPasswordInputType()) { 7916 if (!ArrayUtils.contains(hints, AUTOFILL_HINT_PASSWORD_AUTO)) { 7917 hints = ArrayUtils.appendElement(String.class, hints, 7918 AUTOFILL_HINT_PASSWORD_AUTO); 7919 } 7920 } 7921 return hints; 7922 } 7923 7924 /** 7925 * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise 7926 * a {@code Locale} object that can be used to customize key various listeners. 7927 * @see DateKeyListener#getInstance(Locale) 7928 * @see DateTimeKeyListener#getInstance(Locale) 7929 * @see DigitsKeyListener#getInstance(Locale) 7930 * @see TimeKeyListener#getInstance(Locale) 7931 */ 7932 @Nullable getCustomLocaleForKeyListenerOrNull()7933 private Locale getCustomLocaleForKeyListenerOrNull() { 7934 if (!mUseInternationalizedInput) { 7935 // If the application does not target O, stick to the previous behavior. 7936 return null; 7937 } 7938 final LocaleList locales = getImeHintLocales(); 7939 if (locales == null) { 7940 // If the application does not explicitly specify IME hint locale, also stick to the 7941 // previous behavior. 7942 return null; 7943 } 7944 return locales.get(0); 7945 } 7946 7947 @UnsupportedAppUsage setInputType(int type, boolean direct)7948 private void setInputType(int type, boolean direct) { 7949 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 7950 KeyListener input; 7951 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 7952 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 7953 TextKeyListener.Capitalize cap; 7954 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 7955 cap = TextKeyListener.Capitalize.CHARACTERS; 7956 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 7957 cap = TextKeyListener.Capitalize.WORDS; 7958 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 7959 cap = TextKeyListener.Capitalize.SENTENCES; 7960 } else { 7961 cap = TextKeyListener.Capitalize.NONE; 7962 } 7963 input = TextKeyListener.getInstance(autotext, cap); 7964 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 7965 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 7966 input = DigitsKeyListener.getInstance( 7967 locale, 7968 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 7969 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 7970 if (locale != null) { 7971 // Override type, if necessary for i18n. 7972 int newType = input.getInputType(); 7973 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS; 7974 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) { 7975 // The class is different from the original class. So we need to override 7976 // 'type'. But we want to keep the password flag if it's there. 7977 if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) { 7978 newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 7979 } 7980 type = newType; 7981 } 7982 } 7983 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 7984 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 7985 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 7986 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 7987 input = DateKeyListener.getInstance(locale); 7988 break; 7989 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 7990 input = TimeKeyListener.getInstance(locale); 7991 break; 7992 default: 7993 input = DateTimeKeyListener.getInstance(locale); 7994 break; 7995 } 7996 if (mUseInternationalizedInput) { 7997 type = input.getInputType(); // Override type, if necessary for i18n. 7998 } 7999 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 8000 input = DialerKeyListener.getInstance(); 8001 } else { 8002 input = TextKeyListener.getInstance(); 8003 } 8004 setRawInputType(type); 8005 mListenerChanged = false; 8006 if (direct) { 8007 createEditorIfNeeded(); 8008 mEditor.mKeyListener = input; 8009 } else { 8010 setKeyListenerOnly(input); 8011 } 8012 } 8013 8014 /** 8015 * Get the type of the editable content. 8016 * 8017 * @see #setInputType(int) 8018 * @see android.text.InputType 8019 */ 8020 @InspectableProperty(flagMapping = { 8021 @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL), 8022 @FlagEntry( 8023 name = "text", 8024 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8025 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL), 8026 @FlagEntry( 8027 name = "textUri", 8028 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8029 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI), 8030 @FlagEntry( 8031 name = "textEmailAddress", 8032 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8033 target = InputType.TYPE_CLASS_TEXT 8034 | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS), 8035 @FlagEntry( 8036 name = "textEmailSubject", 8037 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8038 target = InputType.TYPE_CLASS_TEXT 8039 | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT), 8040 @FlagEntry( 8041 name = "textShortMessage", 8042 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8043 target = InputType.TYPE_CLASS_TEXT 8044 | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE), 8045 @FlagEntry( 8046 name = "textLongMessage", 8047 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8048 target = InputType.TYPE_CLASS_TEXT 8049 | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE), 8050 @FlagEntry( 8051 name = "textPersonName", 8052 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8053 target = InputType.TYPE_CLASS_TEXT 8054 | InputType.TYPE_TEXT_VARIATION_PERSON_NAME), 8055 @FlagEntry( 8056 name = "textPostalAddress", 8057 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8058 target = InputType.TYPE_CLASS_TEXT 8059 | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS), 8060 @FlagEntry( 8061 name = "textPassword", 8062 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8063 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD), 8064 @FlagEntry( 8065 name = "textVisiblePassword", 8066 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8067 target = InputType.TYPE_CLASS_TEXT 8068 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD), 8069 @FlagEntry( 8070 name = "textWebEditText", 8071 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8072 target = InputType.TYPE_CLASS_TEXT 8073 | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT), 8074 @FlagEntry( 8075 name = "textFilter", 8076 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8077 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER), 8078 @FlagEntry( 8079 name = "textPhonetic", 8080 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8081 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC), 8082 @FlagEntry( 8083 name = "textWebEmailAddress", 8084 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8085 target = InputType.TYPE_CLASS_TEXT 8086 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS), 8087 @FlagEntry( 8088 name = "textWebPassword", 8089 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8090 target = InputType.TYPE_CLASS_TEXT 8091 | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD), 8092 @FlagEntry( 8093 name = "number", 8094 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8095 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL), 8096 @FlagEntry( 8097 name = "numberPassword", 8098 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8099 target = InputType.TYPE_CLASS_NUMBER 8100 | InputType.TYPE_NUMBER_VARIATION_PASSWORD), 8101 @FlagEntry( 8102 name = "phone", 8103 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8104 target = InputType.TYPE_CLASS_PHONE), 8105 @FlagEntry( 8106 name = "datetime", 8107 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8108 target = InputType.TYPE_CLASS_DATETIME 8109 | InputType.TYPE_DATETIME_VARIATION_NORMAL), 8110 @FlagEntry( 8111 name = "date", 8112 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8113 target = InputType.TYPE_CLASS_DATETIME 8114 | InputType.TYPE_DATETIME_VARIATION_DATE), 8115 @FlagEntry( 8116 name = "time", 8117 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 8118 target = InputType.TYPE_CLASS_DATETIME 8119 | InputType.TYPE_DATETIME_VARIATION_TIME), 8120 @FlagEntry( 8121 name = "textCapCharacters", 8122 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8123 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS), 8124 @FlagEntry( 8125 name = "textCapWords", 8126 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8127 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS), 8128 @FlagEntry( 8129 name = "textCapSentences", 8130 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8131 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), 8132 @FlagEntry( 8133 name = "textAutoCorrect", 8134 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8135 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT), 8136 @FlagEntry( 8137 name = "textAutoComplete", 8138 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8139 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE), 8140 @FlagEntry( 8141 name = "textMultiLine", 8142 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8143 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE), 8144 @FlagEntry( 8145 name = "textImeMultiLine", 8146 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8147 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE), 8148 @FlagEntry( 8149 name = "textNoSuggestions", 8150 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8151 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS), 8152 @FlagEntry( 8153 name = "numberSigned", 8154 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8155 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED), 8156 @FlagEntry( 8157 name = "numberDecimal", 8158 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 8159 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL), 8160 }) getInputType()8161 public int getInputType() { 8162 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 8163 } 8164 8165 /** 8166 * Change the editor type integer associated with the text view, which 8167 * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions} 8168 * when it has focus. 8169 * @see #getImeOptions 8170 * @see android.view.inputmethod.EditorInfo 8171 * @attr ref android.R.styleable#TextView_imeOptions 8172 */ setImeOptions(int imeOptions)8173 public void setImeOptions(int imeOptions) { 8174 createEditorIfNeeded(); 8175 mEditor.createInputContentTypeIfNeeded(); 8176 mEditor.mInputContentType.imeOptions = imeOptions; 8177 } 8178 8179 /** 8180 * Get the type of the Input Method Editor (IME). 8181 * @return the type of the IME 8182 * @see #setImeOptions(int) 8183 * @see EditorInfo 8184 */ 8185 @InspectableProperty(flagMapping = { 8186 @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL), 8187 @FlagEntry( 8188 name = "actionUnspecified", 8189 mask = EditorInfo.IME_MASK_ACTION, 8190 target = EditorInfo.IME_ACTION_UNSPECIFIED), 8191 @FlagEntry( 8192 name = "actionNone", 8193 mask = EditorInfo.IME_MASK_ACTION, 8194 target = EditorInfo.IME_ACTION_NONE), 8195 @FlagEntry( 8196 name = "actionGo", 8197 mask = EditorInfo.IME_MASK_ACTION, 8198 target = EditorInfo.IME_ACTION_GO), 8199 @FlagEntry( 8200 name = "actionSearch", 8201 mask = EditorInfo.IME_MASK_ACTION, 8202 target = EditorInfo.IME_ACTION_SEARCH), 8203 @FlagEntry( 8204 name = "actionSend", 8205 mask = EditorInfo.IME_MASK_ACTION, 8206 target = EditorInfo.IME_ACTION_SEND), 8207 @FlagEntry( 8208 name = "actionNext", 8209 mask = EditorInfo.IME_MASK_ACTION, 8210 target = EditorInfo.IME_ACTION_NEXT), 8211 @FlagEntry( 8212 name = "actionDone", 8213 mask = EditorInfo.IME_MASK_ACTION, 8214 target = EditorInfo.IME_ACTION_DONE), 8215 @FlagEntry( 8216 name = "actionPrevious", 8217 mask = EditorInfo.IME_MASK_ACTION, 8218 target = EditorInfo.IME_ACTION_PREVIOUS), 8219 @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII), 8220 @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT), 8221 @FlagEntry( 8222 name = "flagNavigatePrevious", 8223 target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS), 8224 @FlagEntry( 8225 name = "flagNoAccessoryAction", 8226 target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION), 8227 @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION), 8228 @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI), 8229 @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN), 8230 @FlagEntry( 8231 name = "flagNoPersonalizedLearning", 8232 target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING), 8233 }) getImeOptions()8234 public int getImeOptions() { 8235 return mEditor != null && mEditor.mInputContentType != null 8236 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 8237 } 8238 8239 /** 8240 * Change the custom IME action associated with the text view, which 8241 * will be reported to an IME with {@link EditorInfo#actionLabel} 8242 * and {@link EditorInfo#actionId} when it has focus. 8243 * @see #getImeActionLabel 8244 * @see #getImeActionId 8245 * @see android.view.inputmethod.EditorInfo 8246 * @attr ref android.R.styleable#TextView_imeActionLabel 8247 * @attr ref android.R.styleable#TextView_imeActionId 8248 */ setImeActionLabel(CharSequence label, int actionId)8249 public void setImeActionLabel(CharSequence label, int actionId) { 8250 createEditorIfNeeded(); 8251 mEditor.createInputContentTypeIfNeeded(); 8252 mEditor.mInputContentType.imeActionLabel = label; 8253 mEditor.mInputContentType.imeActionId = actionId; 8254 } 8255 8256 /** 8257 * Get the IME action label previous set with {@link #setImeActionLabel}. 8258 * 8259 * @see #setImeActionLabel 8260 * @see android.view.inputmethod.EditorInfo 8261 */ 8262 @InspectableProperty getImeActionLabel()8263 public CharSequence getImeActionLabel() { 8264 return mEditor != null && mEditor.mInputContentType != null 8265 ? mEditor.mInputContentType.imeActionLabel : null; 8266 } 8267 8268 /** 8269 * Get the IME action ID previous set with {@link #setImeActionLabel}. 8270 * 8271 * @see #setImeActionLabel 8272 * @see android.view.inputmethod.EditorInfo 8273 */ 8274 @InspectableProperty getImeActionId()8275 public int getImeActionId() { 8276 return mEditor != null && mEditor.mInputContentType != null 8277 ? mEditor.mInputContentType.imeActionId : 0; 8278 } 8279 8280 /** 8281 * Set a special listener to be called when an action is performed 8282 * on the text view. This will be called when the enter key is pressed, 8283 * or when an action supplied to the IME is selected by the user. Setting 8284 * this means that the normal hard key event will not insert a newline 8285 * into the text view, even if it is multi-line; holding down the ALT 8286 * modifier will, however, allow the user to insert a newline character. 8287 */ setOnEditorActionListener(OnEditorActionListener l)8288 public void setOnEditorActionListener(OnEditorActionListener l) { 8289 createEditorIfNeeded(); 8290 mEditor.createInputContentTypeIfNeeded(); 8291 mEditor.mInputContentType.onEditorActionListener = l; 8292 } 8293 8294 /** 8295 * Called when an attached input method calls 8296 * {@link InputConnection#performEditorAction(int) 8297 * InputConnection.performEditorAction()} 8298 * for this text view. The default implementation will call your action 8299 * listener supplied to {@link #setOnEditorActionListener}, or perform 8300 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 8301 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 8302 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 8303 * EditorInfo.IME_ACTION_DONE}. 8304 * 8305 * <p>For backwards compatibility, if no IME options have been set and the 8306 * text view would not normally advance focus on enter, then 8307 * the NEXT and DONE actions received here will be turned into an enter 8308 * key down/up pair to go through the normal key handling. 8309 * 8310 * @param actionCode The code of the action being performed. 8311 * 8312 * @see #setOnEditorActionListener 8313 */ onEditorAction(int actionCode)8314 public void onEditorAction(int actionCode) { 8315 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 8316 if (ict != null) { 8317 if (ict.onEditorActionListener != null) { 8318 if (ict.onEditorActionListener.onEditorAction(this, 8319 actionCode, null)) { 8320 return; 8321 } 8322 } 8323 8324 // This is the handling for some default action. 8325 // Note that for backwards compatibility we don't do this 8326 // default handling if explicit ime options have not been given, 8327 // instead turning this into the normal enter key codes that an 8328 // app may be expecting. 8329 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 8330 View v = focusSearch(FOCUS_FORWARD); 8331 if (v != null) { 8332 if (!v.requestFocus(FOCUS_FORWARD)) { 8333 throw new IllegalStateException("focus search returned a view " 8334 + "that wasn't able to take focus!"); 8335 } 8336 } 8337 return; 8338 8339 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 8340 View v = focusSearch(FOCUS_BACKWARD); 8341 if (v != null) { 8342 if (!v.requestFocus(FOCUS_BACKWARD)) { 8343 throw new IllegalStateException("focus search returned a view " 8344 + "that wasn't able to take focus!"); 8345 } 8346 } 8347 return; 8348 8349 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 8350 InputMethodManager imm = getInputMethodManager(); 8351 if (imm != null) { 8352 imm.hideSoftInputFromView(this, 0); 8353 } 8354 return; 8355 } 8356 } 8357 8358 ViewRootImpl viewRootImpl = getViewRootImpl(); 8359 if (viewRootImpl != null) { 8360 long eventTime = SystemClock.uptimeMillis(); 8361 viewRootImpl.dispatchKeyFromIme( 8362 new KeyEvent(eventTime, eventTime, 8363 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 8364 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 8365 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 8366 | KeyEvent.FLAG_EDITOR_ACTION)); 8367 viewRootImpl.dispatchKeyFromIme( 8368 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 8369 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 8370 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 8371 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 8372 | KeyEvent.FLAG_EDITOR_ACTION)); 8373 } 8374 } 8375 8376 /** 8377 * Set the private content type of the text, which is the 8378 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 8379 * field that will be filled in when creating an input connection. 8380 * 8381 * @see #getPrivateImeOptions() 8382 * @see EditorInfo#privateImeOptions 8383 * @attr ref android.R.styleable#TextView_privateImeOptions 8384 */ setPrivateImeOptions(String type)8385 public void setPrivateImeOptions(String type) { 8386 createEditorIfNeeded(); 8387 mEditor.createInputContentTypeIfNeeded(); 8388 mEditor.mInputContentType.privateImeOptions = type; 8389 } 8390 8391 /** 8392 * Get the private type of the content. 8393 * 8394 * @see #setPrivateImeOptions(String) 8395 * @see EditorInfo#privateImeOptions 8396 */ 8397 @InspectableProperty getPrivateImeOptions()8398 public String getPrivateImeOptions() { 8399 return mEditor != null && mEditor.mInputContentType != null 8400 ? mEditor.mInputContentType.privateImeOptions : null; 8401 } 8402 8403 /** 8404 * Set the extra input data of the text, which is the 8405 * {@link EditorInfo#extras TextBoxAttribute.extras} 8406 * Bundle that will be filled in when creating an input connection. The 8407 * given integer is the resource identifier of an XML resource holding an 8408 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 8409 * 8410 * @see #getInputExtras(boolean) 8411 * @see EditorInfo#extras 8412 * @attr ref android.R.styleable#TextView_editorExtras 8413 */ setInputExtras(@mlRes int xmlResId)8414 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException { 8415 createEditorIfNeeded(); 8416 XmlResourceParser parser = getResources().getXml(xmlResId); 8417 mEditor.createInputContentTypeIfNeeded(); 8418 mEditor.mInputContentType.extras = new Bundle(); 8419 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 8420 } 8421 8422 /** 8423 * Retrieve the input extras currently associated with the text view, which 8424 * can be viewed as well as modified. 8425 * 8426 * @param create If true, the extras will be created if they don't already 8427 * exist. Otherwise, null will be returned if none have been created. 8428 * @see #setInputExtras(int) 8429 * @see EditorInfo#extras 8430 * @attr ref android.R.styleable#TextView_editorExtras 8431 */ getInputExtras(boolean create)8432 public Bundle getInputExtras(boolean create) { 8433 if (mEditor == null && !create) return null; 8434 createEditorIfNeeded(); 8435 if (mEditor.mInputContentType == null) { 8436 if (!create) return null; 8437 mEditor.createInputContentTypeIfNeeded(); 8438 } 8439 if (mEditor.mInputContentType.extras == null) { 8440 if (!create) return null; 8441 mEditor.mInputContentType.extras = new Bundle(); 8442 } 8443 return mEditor.mInputContentType.extras; 8444 } 8445 8446 /** 8447 * Change "hint" locales associated with the text view, which will be reported to an IME with 8448 * {@link EditorInfo#hintLocales} when it has focus. 8449 * 8450 * Starting with Android O, this also causes internationalized listeners to be created (or 8451 * change locale) based on the first locale in the input locale list. 8452 * 8453 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to 8454 * call {@link InputMethodManager#restartInput(View)}.</p> 8455 * @param hintLocales List of the languages that the user is supposed to switch to no matter 8456 * what input method subtype is currently used. Set {@code null} to clear the current "hint". 8457 * @see #getImeHintLocales() 8458 * @see android.view.inputmethod.EditorInfo#hintLocales 8459 */ setImeHintLocales(@ullable LocaleList hintLocales)8460 public void setImeHintLocales(@Nullable LocaleList hintLocales) { 8461 createEditorIfNeeded(); 8462 mEditor.createInputContentTypeIfNeeded(); 8463 mEditor.mInputContentType.imeHintLocales = hintLocales; 8464 if (mUseInternationalizedInput) { 8465 changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0)); 8466 } 8467 } 8468 8469 /** 8470 * @return The current languages list "hint". {@code null} when no "hint" is available. 8471 * @see #setImeHintLocales(LocaleList) 8472 * @see android.view.inputmethod.EditorInfo#hintLocales 8473 */ 8474 @Nullable getImeHintLocales()8475 public LocaleList getImeHintLocales() { 8476 if (mEditor == null) { 8477 return null; 8478 } 8479 if (mEditor.mInputContentType == null) { 8480 return null; 8481 } 8482 return mEditor.mInputContentType.imeHintLocales; 8483 } 8484 8485 /** 8486 * Returns the error message that was set to be displayed with 8487 * {@link #setError}, or <code>null</code> if no error was set 8488 * or if it the error was cleared by the widget after user input. 8489 */ getError()8490 public CharSequence getError() { 8491 return mEditor == null ? null : mEditor.mError; 8492 } 8493 8494 /** 8495 * Sets the right-hand compound drawable of the TextView to the "error" 8496 * icon and sets an error message that will be displayed in a popup when 8497 * the TextView has focus. The icon and error message will be reset to 8498 * null when any key events cause changes to the TextView's text. If the 8499 * <code>error</code> is <code>null</code>, the error message and icon 8500 * will be cleared. 8501 */ 8502 @android.view.RemotableViewMethod setError(CharSequence error)8503 public void setError(CharSequence error) { 8504 if (error == null) { 8505 setError(null, null); 8506 } else { 8507 Drawable dr = getContext().getDrawable( 8508 com.android.internal.R.drawable.indicator_input_error); 8509 8510 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 8511 setError(error, dr); 8512 } 8513 } 8514 8515 /** 8516 * Sets the right-hand compound drawable of the TextView to the specified 8517 * icon and sets an error message that will be displayed in a popup when 8518 * the TextView has focus. The icon and error message will be reset to 8519 * null when any key events cause changes to the TextView's text. The 8520 * drawable must already have had {@link Drawable#setBounds} set on it. 8521 * If the <code>error</code> is <code>null</code>, the error message will 8522 * be cleared (and you should provide a <code>null</code> icon as well). 8523 */ setError(CharSequence error, Drawable icon)8524 public void setError(CharSequence error, Drawable icon) { 8525 createEditorIfNeeded(); 8526 mEditor.setError(error, icon); 8527 notifyViewAccessibilityStateChangedIfNeeded( 8528 AccessibilityEvent.CONTENT_CHANGE_TYPE_ERROR 8529 | AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_INVALID); 8530 } 8531 8532 @Override setFrame(int l, int t, int r, int b)8533 protected boolean setFrame(int l, int t, int r, int b) { 8534 boolean result = super.setFrame(l, t, r, b); 8535 8536 if (mEditor != null) mEditor.setFrame(); 8537 8538 restartMarqueeIfNeeded(); 8539 8540 return result; 8541 } 8542 restartMarqueeIfNeeded()8543 private void restartMarqueeIfNeeded() { 8544 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 8545 mRestartMarquee = false; 8546 startMarquee(); 8547 } 8548 } 8549 8550 /** 8551 * Sets the list of input filters that will be used if the buffer is 8552 * Editable. Has no effect otherwise. 8553 * 8554 * @attr ref android.R.styleable#TextView_maxLength 8555 */ setFilters(InputFilter[] filters)8556 public void setFilters(InputFilter[] filters) { 8557 if (filters == null) { 8558 throw new IllegalArgumentException(); 8559 } 8560 8561 mFilters = filters; 8562 8563 if (mText instanceof Editable) { 8564 setFilters((Editable) mText, filters); 8565 } 8566 } 8567 8568 /** 8569 * Sets the list of input filters on the specified Editable, 8570 * and includes mInput in the list if it is an InputFilter. 8571 */ setFilters(Editable e, InputFilter[] filters)8572 private void setFilters(Editable e, InputFilter[] filters) { 8573 if (mEditor != null) { 8574 final boolean undoFilter = mEditor.mUndoInputFilter != null; 8575 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 8576 int num = 0; 8577 if (undoFilter) num++; 8578 if (keyFilter) num++; 8579 if (num > 0) { 8580 InputFilter[] nf = new InputFilter[filters.length + num]; 8581 8582 System.arraycopy(filters, 0, nf, 0, filters.length); 8583 num = 0; 8584 if (undoFilter) { 8585 nf[filters.length] = mEditor.mUndoInputFilter; 8586 num++; 8587 } 8588 if (keyFilter) { 8589 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 8590 } 8591 8592 e.setFilters(nf); 8593 return; 8594 } 8595 } 8596 e.setFilters(filters); 8597 } 8598 8599 /** 8600 * Returns the current list of input filters. 8601 * 8602 * @attr ref android.R.styleable#TextView_maxLength 8603 */ getFilters()8604 public InputFilter[] getFilters() { 8605 return mFilters; 8606 } 8607 8608 ///////////////////////////////////////////////////////////////////////// 8609 getBoxHeight(Layout l)8610 private int getBoxHeight(Layout l) { 8611 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 8612 int padding = (l == mHintLayout) 8613 ? getCompoundPaddingTop() + getCompoundPaddingBottom() 8614 : getExtendedPaddingTop() + getExtendedPaddingBottom(); 8615 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 8616 } 8617 8618 @UnsupportedAppUsage getVerticalOffset(boolean forceNormal)8619 int getVerticalOffset(boolean forceNormal) { 8620 int voffset = 0; 8621 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 8622 8623 Layout l = mLayout; 8624 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 8625 l = mHintLayout; 8626 } 8627 8628 if (gravity != Gravity.TOP) { 8629 int boxht = getBoxHeight(l); 8630 int textht = l.getHeight(); 8631 8632 if (textht < boxht) { 8633 if (gravity == Gravity.BOTTOM) { 8634 voffset = boxht - textht; 8635 } else { // (gravity == Gravity.CENTER_VERTICAL) 8636 voffset = (boxht - textht) >> 1; 8637 } 8638 } 8639 } 8640 return voffset; 8641 } 8642 getBottomVerticalOffset(boolean forceNormal)8643 private int getBottomVerticalOffset(boolean forceNormal) { 8644 int voffset = 0; 8645 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 8646 8647 Layout l = mLayout; 8648 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 8649 l = mHintLayout; 8650 } 8651 8652 if (gravity != Gravity.BOTTOM) { 8653 int boxht = getBoxHeight(l); 8654 int textht = l.getHeight(); 8655 8656 if (textht < boxht) { 8657 if (gravity == Gravity.TOP) { 8658 voffset = boxht - textht; 8659 } else { // (gravity == Gravity.CENTER_VERTICAL) 8660 voffset = (boxht - textht) >> 1; 8661 } 8662 } 8663 } 8664 return voffset; 8665 } 8666 invalidateCursorPath()8667 void invalidateCursorPath() { 8668 if (mHighlightPathBogus) { 8669 invalidateCursor(); 8670 } else { 8671 final int horizontalPadding = getCompoundPaddingLeft(); 8672 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 8673 8674 if (mEditor.mDrawableForCursor == null) { 8675 synchronized (TEMP_RECTF) { 8676 /* 8677 * The reason for this concern about the thickness of the 8678 * cursor and doing the floor/ceil on the coordinates is that 8679 * some EditTexts (notably textfields in the Browser) have 8680 * anti-aliased text where not all the characters are 8681 * necessarily at integer-multiple locations. This should 8682 * make sure the entire cursor gets invalidated instead of 8683 * sometimes missing half a pixel. 8684 */ 8685 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); 8686 if (thick < 1.0f) { 8687 thick = 1.0f; 8688 } 8689 8690 thick /= 2.0f; 8691 8692 // mHighlightPath is guaranteed to be non null at that point. 8693 mHighlightPath.computeBounds(TEMP_RECTF, false); 8694 8695 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), 8696 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), 8697 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), 8698 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 8699 } 8700 } else { 8701 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 8702 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 8703 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 8704 } 8705 } 8706 } 8707 invalidateCursor()8708 void invalidateCursor() { 8709 int where = getSelectionEnd(); 8710 8711 invalidateCursor(where, where, where); 8712 } 8713 invalidateCursor(int a, int b, int c)8714 private void invalidateCursor(int a, int b, int c) { 8715 if (a >= 0 || b >= 0 || c >= 0) { 8716 int start = Math.min(Math.min(a, b), c); 8717 int end = Math.max(Math.max(a, b), c); 8718 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 8719 } 8720 } 8721 8722 /** 8723 * Invalidates the region of text enclosed between the start and end text offsets. 8724 */ invalidateRegion(int start, int end, boolean invalidateCursor)8725 void invalidateRegion(int start, int end, boolean invalidateCursor) { 8726 if (mLayout == null) { 8727 invalidate(); 8728 } else { 8729 start = originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR); 8730 end = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR); 8731 int lineStart = mLayout.getLineForOffset(start); 8732 int top = mLayout.getLineTop(lineStart); 8733 8734 // This is ridiculous, but the descent from the line above 8735 // can hang down into the line we really want to redraw, 8736 // so we have to invalidate part of the line above to make 8737 // sure everything that needs to be redrawn really is. 8738 // (But not the whole line above, because that would cause 8739 // the same problem with the descenders on the line above it!) 8740 if (lineStart > 0) { 8741 top -= mLayout.getLineDescent(lineStart - 1); 8742 } 8743 8744 int lineEnd; 8745 8746 if (start == end) { 8747 lineEnd = lineStart; 8748 } else { 8749 lineEnd = mLayout.getLineForOffset(end); 8750 } 8751 8752 int bottom = mLayout.getLineBottom(lineEnd); 8753 8754 // mEditor can be null in case selection is set programmatically. 8755 if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) { 8756 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 8757 top = Math.min(top, bounds.top); 8758 bottom = Math.max(bottom, bounds.bottom); 8759 } 8760 8761 final int compoundPaddingLeft = getCompoundPaddingLeft(); 8762 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 8763 8764 int left, right; 8765 if (lineStart == lineEnd && !invalidateCursor) { 8766 left = (int) mLayout.getPrimaryHorizontal(start); 8767 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 8768 left += compoundPaddingLeft; 8769 right += compoundPaddingLeft; 8770 } else { 8771 // Rectangle bounding box when the region spans several lines 8772 left = compoundPaddingLeft; 8773 right = getWidth() - getCompoundPaddingRight(); 8774 } 8775 8776 invalidate(mScrollX + left, verticalPadding + top, 8777 mScrollX + right, verticalPadding + bottom); 8778 } 8779 } 8780 registerForPreDraw()8781 private void registerForPreDraw() { 8782 if (!mPreDrawRegistered) { 8783 getViewTreeObserver().addOnPreDrawListener(this); 8784 mPreDrawRegistered = true; 8785 } 8786 } 8787 unregisterForPreDraw()8788 private void unregisterForPreDraw() { 8789 getViewTreeObserver().removeOnPreDrawListener(this); 8790 mPreDrawRegistered = false; 8791 mPreDrawListenerDetached = false; 8792 } 8793 8794 /** 8795 * {@inheritDoc} 8796 */ 8797 @Override onPreDraw()8798 public boolean onPreDraw() { 8799 if (mLayout == null) { 8800 assumeLayout(); 8801 } 8802 8803 if (mMovement != null) { 8804 /* This code also provides auto-scrolling when a cursor is moved using a 8805 * CursorController (insertion point or selection limits). 8806 * For selection, ensure start or end is visible depending on controller's state. 8807 */ 8808 int curs = getSelectionEnd(); 8809 // Do not create the controller if it is not already created. 8810 if (mEditor != null && mEditor.mSelectionModifierCursorController != null 8811 && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 8812 curs = getSelectionStart(); 8813 } 8814 8815 /* 8816 * TODO: This should really only keep the end in view if 8817 * it already was before the text changed. I'm not sure 8818 * of a good way to tell from here if it was. 8819 */ 8820 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 8821 curs = mText.length(); 8822 } 8823 8824 if (curs >= 0) { 8825 bringPointIntoView(curs); 8826 } 8827 } else { 8828 bringTextIntoView(); 8829 } 8830 8831 // This has to be checked here since: 8832 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 8833 // a screen rotation) since layout is not yet initialized at that point. 8834 if (mEditor != null && mEditor.mCreatedWithASelection) { 8835 mEditor.refreshTextActionMode(); 8836 mEditor.mCreatedWithASelection = false; 8837 } 8838 8839 unregisterForPreDraw(); 8840 8841 return true; 8842 } 8843 8844 @Override onAttachedToWindow()8845 protected void onAttachedToWindow() { 8846 super.onAttachedToWindow(); 8847 8848 if (mEditor != null) mEditor.onAttachedToWindow(); 8849 8850 if (mPreDrawListenerDetached) { 8851 getViewTreeObserver().addOnPreDrawListener(this); 8852 mPreDrawListenerDetached = false; 8853 } 8854 } 8855 8856 /** @hide */ 8857 @Override onDetachedFromWindowInternal()8858 protected void onDetachedFromWindowInternal() { 8859 if (mPreDrawRegistered) { 8860 getViewTreeObserver().removeOnPreDrawListener(this); 8861 mPreDrawListenerDetached = true; 8862 } 8863 8864 resetResolvedDrawables(); 8865 8866 if (mEditor != null) mEditor.onDetachedFromWindow(); 8867 8868 super.onDetachedFromWindowInternal(); 8869 } 8870 8871 @Override onScreenStateChanged(int screenState)8872 public void onScreenStateChanged(int screenState) { 8873 super.onScreenStateChanged(screenState); 8874 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 8875 } 8876 8877 @Override isPaddingOffsetRequired()8878 protected boolean isPaddingOffsetRequired() { 8879 return mShadowRadius != 0 || mDrawables != null; 8880 } 8881 8882 @Override getLeftPaddingOffset()8883 protected int getLeftPaddingOffset() { 8884 return getCompoundPaddingLeft() - mPaddingLeft 8885 + (int) Math.min(0, mShadowDx - mShadowRadius); 8886 } 8887 8888 @Override getTopPaddingOffset()8889 protected int getTopPaddingOffset() { 8890 return (int) Math.min(0, mShadowDy - mShadowRadius); 8891 } 8892 8893 @Override getBottomPaddingOffset()8894 protected int getBottomPaddingOffset() { 8895 return (int) Math.max(0, mShadowDy + mShadowRadius); 8896 } 8897 8898 @Override getRightPaddingOffset()8899 protected int getRightPaddingOffset() { 8900 return -(getCompoundPaddingRight() - mPaddingRight) 8901 + (int) Math.max(0, mShadowDx + mShadowRadius); 8902 } 8903 8904 @Override verifyDrawable(@onNull Drawable who)8905 protected boolean verifyDrawable(@NonNull Drawable who) { 8906 final boolean verified = super.verifyDrawable(who); 8907 if (!verified && mDrawables != null) { 8908 for (Drawable dr : mDrawables.mShowing) { 8909 if (who == dr) { 8910 return true; 8911 } 8912 } 8913 } 8914 return verified; 8915 } 8916 8917 @Override jumpDrawablesToCurrentState()8918 public void jumpDrawablesToCurrentState() { 8919 super.jumpDrawablesToCurrentState(); 8920 if (mDrawables != null) { 8921 for (Drawable dr : mDrawables.mShowing) { 8922 if (dr != null) { 8923 dr.jumpToCurrentState(); 8924 } 8925 } 8926 } 8927 } 8928 8929 @Override invalidateDrawable(@onNull Drawable drawable)8930 public void invalidateDrawable(@NonNull Drawable drawable) { 8931 boolean handled = false; 8932 8933 if (verifyDrawable(drawable)) { 8934 final Rect dirty = drawable.getBounds(); 8935 int scrollX = mScrollX; 8936 int scrollY = mScrollY; 8937 8938 // IMPORTANT: The coordinates below are based on the coordinates computed 8939 // for each compound drawable in onDraw(). Make sure to update each section 8940 // accordingly. 8941 final TextView.Drawables drawables = mDrawables; 8942 if (drawables != null) { 8943 if (drawable == drawables.mShowing[Drawables.LEFT]) { 8944 final int compoundPaddingTop = getCompoundPaddingTop(); 8945 final int compoundPaddingBottom = getCompoundPaddingBottom(); 8946 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 8947 8948 scrollX += mPaddingLeft; 8949 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 8950 handled = true; 8951 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { 8952 final int compoundPaddingTop = getCompoundPaddingTop(); 8953 final int compoundPaddingBottom = getCompoundPaddingBottom(); 8954 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 8955 8956 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 8957 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 8958 handled = true; 8959 } else if (drawable == drawables.mShowing[Drawables.TOP]) { 8960 final int compoundPaddingLeft = getCompoundPaddingLeft(); 8961 final int compoundPaddingRight = getCompoundPaddingRight(); 8962 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 8963 8964 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 8965 scrollY += mPaddingTop; 8966 handled = true; 8967 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { 8968 final int compoundPaddingLeft = getCompoundPaddingLeft(); 8969 final int compoundPaddingRight = getCompoundPaddingRight(); 8970 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 8971 8972 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 8973 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 8974 handled = true; 8975 } 8976 } 8977 8978 if (handled) { 8979 invalidate(dirty.left + scrollX, dirty.top + scrollY, 8980 dirty.right + scrollX, dirty.bottom + scrollY); 8981 } 8982 } 8983 8984 if (!handled) { 8985 super.invalidateDrawable(drawable); 8986 } 8987 } 8988 8989 @Override hasOverlappingRendering()8990 public boolean hasOverlappingRendering() { 8991 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 8992 return ((getBackground() != null && getBackground().getCurrent() != null) 8993 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled() 8994 || mShadowColor != 0); 8995 } 8996 8997 /** 8998 * 8999 * Returns the state of the {@code textIsSelectable} flag (See 9000 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 9001 * to allow users to select and copy text in a non-editable TextView, the content of an 9002 * {@link EditText} can always be selected, independently of the value of this flag. 9003 * <p> 9004 * 9005 * @return True if the text displayed in this TextView can be selected by the user. 9006 * 9007 * @attr ref android.R.styleable#TextView_textIsSelectable 9008 */ 9009 @InspectableProperty(name = "textIsSelectable") isTextSelectable()9010 public boolean isTextSelectable() { 9011 return mEditor == null ? false : mEditor.mTextIsSelectable; 9012 } 9013 9014 /** 9015 * Sets whether the content of this view is selectable by the user. The default is 9016 * {@code false}, meaning that the content is not selectable. 9017 * <p> 9018 * When you use a TextView to display a useful piece of information to the user (such as a 9019 * contact's address), make it selectable, so that the user can select and copy its 9020 * content. You can also use set the XML attribute 9021 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 9022 * <p> 9023 * When you call this method to set the value of {@code textIsSelectable}, it sets 9024 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 9025 * and {@code longClickable} to the same value. These flags correspond to the attributes 9026 * {@link android.R.styleable#View_focusable android:focusable}, 9027 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 9028 * {@link android.R.styleable#View_clickable android:clickable}, and 9029 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 9030 * flags to a state you had set previously, call one or more of the following methods: 9031 * {@link #setFocusable(boolean) setFocusable()}, 9032 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 9033 * {@link #setClickable(boolean) setClickable()} or 9034 * {@link #setLongClickable(boolean) setLongClickable()}. 9035 * 9036 * @param selectable Whether the content of this TextView should be selectable. 9037 */ setTextIsSelectable(boolean selectable)9038 public void setTextIsSelectable(boolean selectable) { 9039 if (!selectable && mEditor == null) return; // false is default value with no edit data 9040 9041 createEditorIfNeeded(); 9042 if (mEditor.mTextIsSelectable == selectable) return; 9043 9044 mEditor.mTextIsSelectable = selectable; 9045 setFocusableInTouchMode(selectable); 9046 setFocusable(FOCUSABLE_AUTO); 9047 setClickable(selectable); 9048 setLongClickable(selectable); 9049 9050 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 9051 9052 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 9053 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 9054 9055 // Called by setText above, but safer in case of future code changes 9056 mEditor.prepareCursorControllers(); 9057 } 9058 9059 @Override onCreateDrawableState(int extraSpace)9060 protected int[] onCreateDrawableState(int extraSpace) { 9061 final int[] drawableState; 9062 9063 if (mSingleLine) { 9064 drawableState = super.onCreateDrawableState(extraSpace); 9065 } else { 9066 drawableState = super.onCreateDrawableState(extraSpace + 1); 9067 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 9068 } 9069 9070 if (isTextSelectable()) { 9071 // Disable pressed state, which was introduced when TextView was made clickable. 9072 // Prevents text color change. 9073 // setClickable(false) would have a similar effect, but it also disables focus changes 9074 // and long press actions, which are both needed by text selection. 9075 final int length = drawableState.length; 9076 for (int i = 0; i < length; i++) { 9077 if (drawableState[i] == R.attr.state_pressed) { 9078 final int[] nonPressedState = new int[length - 1]; 9079 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 9080 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 9081 return nonPressedState; 9082 } 9083 } 9084 } 9085 9086 return drawableState; 9087 } 9088 maybeUpdateHighlightPaths()9089 private void maybeUpdateHighlightPaths() { 9090 if (!mHighlightPathsBogus) { 9091 return; 9092 } 9093 9094 if (mHighlightPaths != null) { 9095 mPathRecyclePool.addAll(mHighlightPaths); 9096 mHighlightPaths.clear(); 9097 mHighlightPaints.clear(); 9098 } else { 9099 mHighlightPaths = new ArrayList<>(); 9100 mHighlightPaints = new ArrayList<>(); 9101 } 9102 9103 if (mHighlights != null) { 9104 for (int i = 0; i < mHighlights.getSize(); ++i) { 9105 final int[] ranges = mHighlights.getRanges(i); 9106 final Paint paint = mHighlights.getPaint(i); 9107 final Path path; 9108 if (mPathRecyclePool.isEmpty()) { 9109 path = new Path(); 9110 } else { 9111 path = mPathRecyclePool.get(mPathRecyclePool.size() - 1); 9112 mPathRecyclePool.remove(mPathRecyclePool.size() - 1); 9113 path.reset(); 9114 } 9115 9116 boolean atLeastOnePathAdded = false; 9117 for (int j = 0; j < ranges.length / 2; ++j) { 9118 final int start = ranges[2 * j]; 9119 final int end = ranges[2 * j + 1]; 9120 if (start < end) { 9121 mLayout.getSelection(start, end, (left, top, right, bottom, layout) -> 9122 path.addRect(left, top, right, bottom, Path.Direction.CW) 9123 ); 9124 atLeastOnePathAdded = true; 9125 } 9126 } 9127 if (atLeastOnePathAdded) { 9128 mHighlightPaths.add(path); 9129 mHighlightPaints.add(paint); 9130 } 9131 } 9132 } 9133 9134 addSearchHighlightPaths(); 9135 9136 if (hasGesturePreviewHighlight()) { 9137 final Path path; 9138 if (mPathRecyclePool.isEmpty()) { 9139 path = new Path(); 9140 } else { 9141 path = mPathRecyclePool.get(mPathRecyclePool.size() - 1); 9142 mPathRecyclePool.remove(mPathRecyclePool.size() - 1); 9143 path.reset(); 9144 } 9145 mLayout.getSelectionPath( 9146 mGesturePreviewHighlightStart, mGesturePreviewHighlightEnd, path); 9147 mHighlightPaths.add(path); 9148 mHighlightPaints.add(mGesturePreviewHighlightPaint); 9149 } 9150 9151 mHighlightPathsBogus = false; 9152 } 9153 addSearchHighlightPaths()9154 private void addSearchHighlightPaths() { 9155 if (mSearchResultHighlights != null) { 9156 final Path searchResultPath; 9157 if (mPathRecyclePool.isEmpty()) { 9158 searchResultPath = new Path(); 9159 } else { 9160 searchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1); 9161 mPathRecyclePool.remove(mPathRecyclePool.size() - 1); 9162 searchResultPath.reset(); 9163 } 9164 final Path focusedSearchResultPath; 9165 if (mFocusedSearchResultIndex == FOCUSED_SEARCH_RESULT_INDEX_NONE) { 9166 focusedSearchResultPath = null; 9167 } else if (mPathRecyclePool.isEmpty()) { 9168 focusedSearchResultPath = new Path(); 9169 } else { 9170 focusedSearchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1); 9171 mPathRecyclePool.remove(mPathRecyclePool.size() - 1); 9172 focusedSearchResultPath.reset(); 9173 } 9174 9175 boolean atLeastOnePathAdded = false; 9176 for (int j = 0; j < mSearchResultHighlights.length / 2; ++j) { 9177 final int start = mSearchResultHighlights[2 * j]; 9178 final int end = mSearchResultHighlights[2 * j + 1]; 9179 if (start < end) { 9180 if (j == mFocusedSearchResultIndex) { 9181 mLayout.getSelection(start, end, (left, top, right, bottom, layout) -> 9182 focusedSearchResultPath.addRect(left, top, right, bottom, 9183 Path.Direction.CW) 9184 ); 9185 } else { 9186 mLayout.getSelection(start, end, (left, top, right, bottom, layout) -> 9187 searchResultPath.addRect(left, top, right, bottom, 9188 Path.Direction.CW) 9189 ); 9190 atLeastOnePathAdded = true; 9191 } 9192 } 9193 } 9194 if (atLeastOnePathAdded) { 9195 if (mSearchResultHighlightPaint == null) { 9196 mSearchResultHighlightPaint = new Paint(); 9197 } 9198 mSearchResultHighlightPaint.setColor(mSearchResultHighlightColor); 9199 mSearchResultHighlightPaint.setStyle(Paint.Style.FILL); 9200 mHighlightPaths.add(searchResultPath); 9201 mHighlightPaints.add(mSearchResultHighlightPaint); 9202 } 9203 if (focusedSearchResultPath != null) { 9204 if (mFocusedSearchResultHighlightPaint == null) { 9205 mFocusedSearchResultHighlightPaint = new Paint(); 9206 } 9207 mFocusedSearchResultHighlightPaint.setColor(mFocusedSearchResultHighlightColor); 9208 mFocusedSearchResultHighlightPaint.setStyle(Paint.Style.FILL); 9209 mHighlightPaths.add(focusedSearchResultPath); 9210 mHighlightPaints.add(mFocusedSearchResultHighlightPaint); 9211 } 9212 } 9213 } 9214 9215 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getUpdatedHighlightPath()9216 private Path getUpdatedHighlightPath() { 9217 Path highlight = null; 9218 Paint highlightPaint = mHighlightPaint; 9219 9220 final int selStart = getSelectionStartTransformed(); 9221 final int selEnd = getSelectionEndTransformed(); 9222 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 9223 if (selStart == selEnd) { 9224 if (mEditor != null && mEditor.shouldRenderCursor()) { 9225 if (mHighlightPathBogus) { 9226 if (mHighlightPath == null) mHighlightPath = new Path(); 9227 mHighlightPath.reset(); 9228 mLayout.getCursorPath(selStart, mHighlightPath, mText); 9229 mEditor.updateCursorPosition(); 9230 mHighlightPathBogus = false; 9231 } 9232 9233 // XXX should pass to skin instead of drawing directly 9234 highlightPaint.setColor(mCurTextColor); 9235 highlightPaint.setStyle(Paint.Style.STROKE); 9236 highlight = mHighlightPath; 9237 } 9238 } else { 9239 if (mHighlightPathBogus) { 9240 if (mHighlightPath == null) mHighlightPath = new Path(); 9241 mHighlightPath.reset(); 9242 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 9243 mHighlightPathBogus = false; 9244 } 9245 9246 // XXX should pass to skin instead of drawing directly 9247 highlightPaint.setColor(mHighlightColor); 9248 highlightPaint.setStyle(Paint.Style.FILL); 9249 9250 highlight = mHighlightPath; 9251 } 9252 } 9253 return highlight; 9254 } 9255 9256 /** 9257 * @hide 9258 */ getHorizontalOffsetForDrawables()9259 public int getHorizontalOffsetForDrawables() { 9260 return 0; 9261 } 9262 9263 @Override onDraw(Canvas canvas)9264 protected void onDraw(Canvas canvas) { 9265 restartMarqueeIfNeeded(); 9266 9267 // Draw the background for this view 9268 super.onDraw(canvas); 9269 9270 final int compoundPaddingLeft = getCompoundPaddingLeft(); 9271 final int compoundPaddingTop = getCompoundPaddingTop(); 9272 final int compoundPaddingRight = getCompoundPaddingRight(); 9273 final int compoundPaddingBottom = getCompoundPaddingBottom(); 9274 final int scrollX = mScrollX; 9275 final int scrollY = mScrollY; 9276 final int right = mRight; 9277 final int left = mLeft; 9278 final int bottom = mBottom; 9279 final int top = mTop; 9280 final boolean isLayoutRtl = isLayoutRtl(); 9281 final int offset = getHorizontalOffsetForDrawables(); 9282 final int leftOffset = isLayoutRtl ? 0 : offset; 9283 final int rightOffset = isLayoutRtl ? offset : 0; 9284 9285 final Drawables dr = mDrawables; 9286 if (dr != null) { 9287 /* 9288 * Compound, not extended, because the icon is not clipped 9289 * if the text height is smaller. 9290 */ 9291 9292 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 9293 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 9294 9295 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 9296 // Make sure to update invalidateDrawable() when changing this code. 9297 if (dr.mShowing[Drawables.LEFT] != null) { 9298 canvas.save(); 9299 canvas.translate(scrollX + mPaddingLeft + leftOffset, 9300 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); 9301 dr.mShowing[Drawables.LEFT].draw(canvas); 9302 canvas.restore(); 9303 } 9304 9305 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 9306 // Make sure to update invalidateDrawable() when changing this code. 9307 if (dr.mShowing[Drawables.RIGHT] != null) { 9308 canvas.save(); 9309 canvas.translate(scrollX + right - left - mPaddingRight 9310 - dr.mDrawableSizeRight - rightOffset, 9311 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 9312 dr.mShowing[Drawables.RIGHT].draw(canvas); 9313 canvas.restore(); 9314 } 9315 9316 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 9317 // Make sure to update invalidateDrawable() when changing this code. 9318 if (dr.mShowing[Drawables.TOP] != null) { 9319 canvas.save(); 9320 canvas.translate(scrollX + compoundPaddingLeft 9321 + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 9322 dr.mShowing[Drawables.TOP].draw(canvas); 9323 canvas.restore(); 9324 } 9325 9326 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 9327 // Make sure to update invalidateDrawable() when changing this code. 9328 if (dr.mShowing[Drawables.BOTTOM] != null) { 9329 canvas.save(); 9330 canvas.translate(scrollX + compoundPaddingLeft 9331 + (hspace - dr.mDrawableWidthBottom) / 2, 9332 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 9333 dr.mShowing[Drawables.BOTTOM].draw(canvas); 9334 canvas.restore(); 9335 } 9336 } 9337 9338 int color = mCurTextColor; 9339 9340 if (mLayout == null) { 9341 assumeLayout(); 9342 } 9343 9344 Layout layout = mLayout; 9345 9346 if (mHint != null && !mHideHint && mText.length() == 0) { 9347 if (mHintTextColor != null) { 9348 color = mCurHintTextColor; 9349 } 9350 9351 layout = mHintLayout; 9352 } 9353 9354 mTextPaint.setColor(color); 9355 mTextPaint.drawableState = getDrawableState(); 9356 9357 canvas.save(); 9358 /* Would be faster if we didn't have to do this. Can we chop the 9359 (displayable) text so that we don't need to do this ever? 9360 */ 9361 9362 int extendedPaddingTop = getExtendedPaddingTop(); 9363 int extendedPaddingBottom = getExtendedPaddingBottom(); 9364 9365 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 9366 final int maxScrollY = mLayout.getHeight() - vspace; 9367 9368 float clipLeft = compoundPaddingLeft + scrollX; 9369 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 9370 float clipRight = right - left - getCompoundPaddingRight() + scrollX; 9371 float clipBottom = bottom - top + scrollY 9372 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 9373 9374 if (mShadowRadius != 0) { 9375 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 9376 clipRight += Math.max(0, mShadowDx + mShadowRadius); 9377 9378 clipTop += Math.min(0, mShadowDy - mShadowRadius); 9379 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 9380 } 9381 9382 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 9383 9384 int voffsetText = 0; 9385 int voffsetCursor = 0; 9386 9387 // translate in by our padding 9388 /* shortcircuit calling getVerticaOffset() */ 9389 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9390 voffsetText = getVerticalOffset(false); 9391 voffsetCursor = getVerticalOffset(true); 9392 } 9393 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 9394 9395 final int layoutDirection = getLayoutDirection(); 9396 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 9397 if (isMarqueeFadeEnabled()) { 9398 if (!mSingleLine && getLineCount() == 1 && canMarquee() 9399 && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 9400 final int width = mRight - mLeft; 9401 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 9402 final float dx = mLayout.getLineRight(0) - (width - padding); 9403 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 9404 } 9405 9406 if (mMarquee != null && mMarquee.isRunning()) { 9407 final float dx = -mMarquee.getScroll(); 9408 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 9409 } 9410 } 9411 9412 final int cursorOffsetVertical = voffsetCursor - voffsetText; 9413 9414 maybeUpdateHighlightPaths(); 9415 // If there is a gesture preview highlight, then the selection or cursor is not drawn. 9416 Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath(); 9417 if (mEditor != null) { 9418 mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight, 9419 mHighlightPaint, cursorOffsetVertical); 9420 } else { 9421 layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint, 9422 cursorOffsetVertical); 9423 } 9424 9425 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 9426 final float dx = mMarquee.getGhostOffset(); 9427 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 9428 layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint, 9429 cursorOffsetVertical); 9430 } 9431 9432 canvas.restore(); 9433 } 9434 9435 @Override getFocusedRect(Rect r)9436 public void getFocusedRect(Rect r) { 9437 if (mLayout == null) { 9438 super.getFocusedRect(r); 9439 return; 9440 } 9441 9442 int selEnd = getSelectionEndTransformed(); 9443 if (selEnd < 0) { 9444 super.getFocusedRect(r); 9445 return; 9446 } 9447 9448 int selStart = getSelectionStartTransformed(); 9449 if (selStart < 0 || selStart >= selEnd) { 9450 int line = mLayout.getLineForOffset(selEnd); 9451 r.top = mLayout.getLineTop(line); 9452 r.bottom = mLayout.getLineBottom(line); 9453 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 9454 r.right = r.left + 4; 9455 } else { 9456 int lineStart = mLayout.getLineForOffset(selStart); 9457 int lineEnd = mLayout.getLineForOffset(selEnd); 9458 r.top = mLayout.getLineTop(lineStart); 9459 r.bottom = mLayout.getLineBottom(lineEnd); 9460 if (lineStart == lineEnd) { 9461 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 9462 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 9463 } else { 9464 // Selection extends across multiple lines -- make the focused 9465 // rect cover the entire width. 9466 if (mHighlightPathBogus) { 9467 if (mHighlightPath == null) mHighlightPath = new Path(); 9468 mHighlightPath.reset(); 9469 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 9470 mHighlightPathBogus = false; 9471 } 9472 synchronized (TEMP_RECTF) { 9473 mHighlightPath.computeBounds(TEMP_RECTF, true); 9474 r.left = (int) TEMP_RECTF.left - 1; 9475 r.right = (int) TEMP_RECTF.right + 1; 9476 } 9477 } 9478 } 9479 9480 // Adjust for padding and gravity. 9481 int paddingLeft = getCompoundPaddingLeft(); 9482 int paddingTop = getExtendedPaddingTop(); 9483 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9484 paddingTop += getVerticalOffset(false); 9485 } 9486 r.offset(paddingLeft, paddingTop); 9487 int paddingBottom = getExtendedPaddingBottom(); 9488 r.bottom += paddingBottom; 9489 } 9490 9491 /** 9492 * Return the number of lines of text, or 0 if the internal Layout has not 9493 * been built. 9494 */ getLineCount()9495 public int getLineCount() { 9496 return mLayout != null ? mLayout.getLineCount() : 0; 9497 } 9498 9499 /** 9500 * Return the baseline for the specified line (0...getLineCount() - 1) 9501 * If bounds is not null, return the top, left, right, bottom extents 9502 * of the specified line in it. If the internal Layout has not been built, 9503 * return 0 and set bounds to (0, 0, 0, 0) 9504 * @param line which line to examine (0..getLineCount() - 1) 9505 * @param bounds Optional. If not null, it returns the extent of the line 9506 * @return the Y-coordinate of the baseline 9507 */ getLineBounds(int line, Rect bounds)9508 public int getLineBounds(int line, Rect bounds) { 9509 if (mLayout == null) { 9510 if (bounds != null) { 9511 bounds.set(0, 0, 0, 0); 9512 } 9513 return 0; 9514 } else { 9515 int baseline = mLayout.getLineBounds(line, bounds); 9516 9517 int voffset = getExtendedPaddingTop(); 9518 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9519 voffset += getVerticalOffset(true); 9520 } 9521 if (bounds != null) { 9522 bounds.offset(getCompoundPaddingLeft(), voffset); 9523 } 9524 return baseline + voffset; 9525 } 9526 } 9527 9528 @Override getBaseline()9529 public int getBaseline() { 9530 if (mLayout == null) { 9531 return super.getBaseline(); 9532 } 9533 9534 return getBaselineOffset() + mLayout.getLineBaseline(0); 9535 } 9536 getBaselineOffset()9537 int getBaselineOffset() { 9538 int voffset = 0; 9539 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9540 voffset = getVerticalOffset(true); 9541 } 9542 9543 if (isLayoutModeOptical(mParent)) { 9544 voffset -= getOpticalInsets().top; 9545 } 9546 9547 return getExtendedPaddingTop() + voffset; 9548 } 9549 9550 /** 9551 * @hide 9552 */ 9553 @Override getFadeTop(boolean offsetRequired)9554 protected int getFadeTop(boolean offsetRequired) { 9555 if (mLayout == null) return 0; 9556 9557 int voffset = 0; 9558 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9559 voffset = getVerticalOffset(true); 9560 } 9561 9562 if (offsetRequired) voffset += getTopPaddingOffset(); 9563 9564 return getExtendedPaddingTop() + voffset; 9565 } 9566 9567 /** 9568 * @hide 9569 */ 9570 @Override getFadeHeight(boolean offsetRequired)9571 protected int getFadeHeight(boolean offsetRequired) { 9572 return mLayout != null ? mLayout.getHeight() : 0; 9573 } 9574 9575 @Override onResolvePointerIcon(MotionEvent event, int pointerIndex)9576 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 9577 if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { 9578 if (mSpannable != null && mLinksClickable) { 9579 final float x = event.getX(pointerIndex); 9580 final float y = event.getY(pointerIndex); 9581 final int offset = getOffsetForPosition(x, y); 9582 final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset, 9583 ClickableSpan.class); 9584 if (clickables.length > 0) { 9585 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND); 9586 } 9587 } 9588 if (isTextSelectable() || isTextEditable()) { 9589 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT); 9590 } 9591 } 9592 return super.onResolvePointerIcon(event, pointerIndex); 9593 } 9594 9595 @Override onKeyPreIme(int keyCode, KeyEvent event)9596 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 9597 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, 9598 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call 9599 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event). 9600 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) { 9601 return true; 9602 } 9603 return super.onKeyPreIme(keyCode, event); 9604 } 9605 9606 /** 9607 * @hide 9608 */ handleBackInTextActionModeIfNeeded(KeyEvent event)9609 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) { 9610 // Do nothing unless mEditor is in text action mode. 9611 if (mEditor == null || mEditor.getTextActionMode() == null) { 9612 return false; 9613 } 9614 9615 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 9616 KeyEvent.DispatcherState state = getKeyDispatcherState(); 9617 if (state != null) { 9618 state.startTracking(event, this); 9619 } 9620 return true; 9621 } else if (event.getAction() == KeyEvent.ACTION_UP) { 9622 KeyEvent.DispatcherState state = getKeyDispatcherState(); 9623 if (state != null) { 9624 state.handleUpEvent(event); 9625 } 9626 if (event.isTracking() && !event.isCanceled()) { 9627 stopTextActionMode(); 9628 return true; 9629 } 9630 } 9631 return false; 9632 } 9633 9634 @Override onKeyDown(int keyCode, KeyEvent event)9635 public boolean onKeyDown(int keyCode, KeyEvent event) { 9636 final int which = doKeyDown(keyCode, event, null); 9637 if (which == KEY_EVENT_NOT_HANDLED) { 9638 return super.onKeyDown(keyCode, event); 9639 } 9640 9641 return true; 9642 } 9643 9644 @Override onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)9645 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 9646 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 9647 final int which = doKeyDown(keyCode, down, event); 9648 if (which == KEY_EVENT_NOT_HANDLED) { 9649 // Go through default dispatching. 9650 return super.onKeyMultiple(keyCode, repeatCount, event); 9651 } 9652 if (which == KEY_EVENT_HANDLED) { 9653 // Consumed the whole thing. 9654 return true; 9655 } 9656 9657 repeatCount--; 9658 9659 // We are going to dispatch the remaining events to either the input 9660 // or movement method. To do this, we will just send a repeated stream 9661 // of down and up events until we have done the complete repeatCount. 9662 // It would be nice if those interfaces had an onKeyMultiple() method, 9663 // but adding that is a more complicated change. 9664 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 9665 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) { 9666 // mEditor and mEditor.mInput are not null from doKeyDown 9667 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 9668 while (--repeatCount > 0) { 9669 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down); 9670 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 9671 } 9672 hideErrorIfUnchanged(); 9673 9674 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) { 9675 // mMovement is not null from doKeyDown 9676 mMovement.onKeyUp(this, mSpannable, keyCode, up); 9677 while (--repeatCount > 0) { 9678 mMovement.onKeyDown(this, mSpannable, keyCode, down); 9679 mMovement.onKeyUp(this, mSpannable, keyCode, up); 9680 } 9681 } 9682 9683 return true; 9684 } 9685 9686 /** 9687 * Returns true if pressing ENTER in this field advances focus instead 9688 * of inserting the character. This is true mostly in single-line fields, 9689 * but also in mail addresses and subjects which will display on multiple 9690 * lines but where it doesn't make sense to insert newlines. 9691 */ shouldAdvanceFocusOnEnter()9692 private boolean shouldAdvanceFocusOnEnter() { 9693 if (getKeyListener() == null) { 9694 return false; 9695 } 9696 9697 if (mSingleLine) { 9698 return true; 9699 } 9700 9701 if (mEditor != null 9702 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 9703 == EditorInfo.TYPE_CLASS_TEXT) { 9704 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 9705 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 9706 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 9707 return true; 9708 } 9709 } 9710 9711 return false; 9712 } 9713 isDirectionalNavigationKey(int keyCode)9714 private boolean isDirectionalNavigationKey(int keyCode) { 9715 switch(keyCode) { 9716 case KeyEvent.KEYCODE_DPAD_UP: 9717 case KeyEvent.KEYCODE_DPAD_DOWN: 9718 case KeyEvent.KEYCODE_DPAD_LEFT: 9719 case KeyEvent.KEYCODE_DPAD_RIGHT: 9720 return true; 9721 } 9722 return false; 9723 } 9724 doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)9725 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 9726 if (!isEnabled()) { 9727 return KEY_EVENT_NOT_HANDLED; 9728 } 9729 9730 // If this is the initial keydown, we don't want to prevent a movement away from this view. 9731 // While this shouldn't be necessary because any time we're preventing default movement we 9732 // should be restricting the focus to remain within this view, thus we'll also receive 9733 // the key up event, occasionally key up events will get dropped and we don't want to 9734 // prevent the user from traversing out of this on the next key down. 9735 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 9736 mPreventDefaultMovement = false; 9737 } 9738 9739 switch (keyCode) { 9740 case KeyEvent.KEYCODE_ENTER: 9741 case KeyEvent.KEYCODE_NUMPAD_ENTER: 9742 if (event.hasNoModifiers()) { 9743 // When mInputContentType is set, we know that we are 9744 // running in a "modern" cupcake environment, so don't need 9745 // to worry about the application trying to capture 9746 // enter key events. 9747 if (mEditor != null && mEditor.mInputContentType != null) { 9748 // If there is an action listener, given them a 9749 // chance to consume the event. 9750 if (mEditor.mInputContentType.onEditorActionListener != null 9751 && mEditor.mInputContentType.onEditorActionListener.onEditorAction( 9752 this, 9753 getActionIdForEnterEvent(), 9754 event)) { 9755 mEditor.mInputContentType.enterDown = true; 9756 // We are consuming the enter key for them. 9757 return KEY_EVENT_HANDLED; 9758 } 9759 } 9760 9761 // If our editor should move focus when enter is pressed, or 9762 // this is a generated event from an IME action button, then 9763 // don't let it be inserted into the text. 9764 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 9765 || shouldAdvanceFocusOnEnter()) { 9766 if (hasOnClickListeners()) { 9767 return KEY_EVENT_NOT_HANDLED; 9768 } 9769 return KEY_EVENT_HANDLED; 9770 } 9771 } 9772 break; 9773 9774 case KeyEvent.KEYCODE_DPAD_CENTER: 9775 if (event.hasNoModifiers()) { 9776 if (shouldAdvanceFocusOnEnter()) { 9777 return KEY_EVENT_NOT_HANDLED; 9778 } 9779 } 9780 break; 9781 9782 case KeyEvent.KEYCODE_TAB: 9783 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 9784 // Tab is used to move focus. 9785 return KEY_EVENT_NOT_HANDLED; 9786 } 9787 break; 9788 9789 // Has to be done on key down (and not on key up) to correctly be intercepted. 9790 case KeyEvent.KEYCODE_BACK: 9791 if (mEditor != null && mEditor.getTextActionMode() != null) { 9792 stopTextActionMode(); 9793 return KEY_EVENT_HANDLED; 9794 } 9795 break; 9796 9797 case KeyEvent.KEYCODE_ESCAPE: 9798 if (Flags.escapeClearsFocus() && event.hasNoModifiers()) { 9799 if (mEditor != null && mEditor.getTextActionMode() != null) { 9800 stopTextActionMode(); 9801 return KEY_EVENT_HANDLED; 9802 } 9803 if (hasFocus()) { 9804 clearFocusInternal(null, /* propagate */ true, /* refocus */ false); 9805 InputMethodManager imm = getInputMethodManager(); 9806 if (imm != null) { 9807 imm.hideSoftInputFromView(this, 0); 9808 } 9809 return KEY_EVENT_HANDLED; 9810 } 9811 } 9812 break; 9813 9814 case KeyEvent.KEYCODE_CUT: 9815 if (event.hasNoModifiers() && canCut()) { 9816 if (onTextContextMenuItem(ID_CUT)) { 9817 return KEY_EVENT_HANDLED; 9818 } 9819 } 9820 break; 9821 9822 case KeyEvent.KEYCODE_COPY: 9823 if (event.hasNoModifiers() && canCopy()) { 9824 if (onTextContextMenuItem(ID_COPY)) { 9825 return KEY_EVENT_HANDLED; 9826 } 9827 } 9828 break; 9829 9830 case KeyEvent.KEYCODE_PASTE: 9831 if (event.hasNoModifiers() && canPaste()) { 9832 if (onTextContextMenuItem(ID_PASTE)) { 9833 return KEY_EVENT_HANDLED; 9834 } 9835 } 9836 break; 9837 9838 case KeyEvent.KEYCODE_FORWARD_DEL: 9839 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) { 9840 if (onTextContextMenuItem(ID_CUT)) { 9841 return KEY_EVENT_HANDLED; 9842 } 9843 } 9844 break; 9845 9846 case KeyEvent.KEYCODE_INSERT: 9847 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) { 9848 if (onTextContextMenuItem(ID_COPY)) { 9849 return KEY_EVENT_HANDLED; 9850 } 9851 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) { 9852 if (onTextContextMenuItem(ID_PASTE)) { 9853 return KEY_EVENT_HANDLED; 9854 } 9855 } 9856 break; 9857 } 9858 9859 if (mEditor != null && mEditor.mKeyListener != null) { 9860 boolean doDown = true; 9861 if (otherEvent != null) { 9862 try { 9863 beginBatchEdit(); 9864 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 9865 otherEvent); 9866 hideErrorIfUnchanged(); 9867 doDown = false; 9868 if (handled) { 9869 return KEY_EVENT_HANDLED; 9870 } 9871 } catch (AbstractMethodError e) { 9872 // onKeyOther was added after 1.0, so if it isn't 9873 // implemented we need to try to dispatch as a regular down. 9874 } finally { 9875 endBatchEdit(); 9876 } 9877 } 9878 9879 if (doDown) { 9880 beginBatchEdit(); 9881 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 9882 keyCode, event); 9883 endBatchEdit(); 9884 hideErrorIfUnchanged(); 9885 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER; 9886 } 9887 } 9888 9889 // bug 650865: sometimes we get a key event before a layout. 9890 // don't try to move around if we don't know the layout. 9891 9892 if (mMovement != null && mLayout != null) { 9893 boolean doDown = true; 9894 if (otherEvent != null) { 9895 try { 9896 boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent); 9897 doDown = false; 9898 if (handled) { 9899 return KEY_EVENT_HANDLED; 9900 } 9901 } catch (AbstractMethodError e) { 9902 // onKeyOther was added after 1.0, so if it isn't 9903 // implemented we need to try to dispatch as a regular down. 9904 } 9905 } 9906 if (doDown) { 9907 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) { 9908 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 9909 mPreventDefaultMovement = true; 9910 } 9911 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD; 9912 } 9913 } 9914 // Consume arrows from keyboard devices to prevent focus leaving the editor. 9915 // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those 9916 // to move focus with arrows. 9917 if (event.getSource() == InputDevice.SOURCE_KEYBOARD 9918 && isDirectionalNavigationKey(keyCode)) { 9919 return KEY_EVENT_HANDLED; 9920 } 9921 } 9922 9923 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) 9924 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED; 9925 } 9926 9927 /** 9928 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 9929 * can be recorded. 9930 * @hide 9931 */ resetErrorChangedFlag()9932 public void resetErrorChangedFlag() { 9933 /* 9934 * Keep track of what the error was before doing the input 9935 * so that if an input filter changed the error, we leave 9936 * that error showing. Otherwise, we take down whatever 9937 * error was showing when the user types something. 9938 */ 9939 if (mEditor != null) mEditor.mErrorWasChanged = false; 9940 } 9941 9942 /** 9943 * @hide 9944 */ hideErrorIfUnchanged()9945 public void hideErrorIfUnchanged() { 9946 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 9947 setError(null, null); 9948 } 9949 } 9950 9951 @Override onKeyUp(int keyCode, KeyEvent event)9952 public boolean onKeyUp(int keyCode, KeyEvent event) { 9953 if (!isEnabled()) { 9954 return super.onKeyUp(keyCode, event); 9955 } 9956 9957 if (!KeyEvent.isModifierKey(keyCode)) { 9958 mPreventDefaultMovement = false; 9959 } 9960 9961 switch (keyCode) { 9962 case KeyEvent.KEYCODE_DPAD_CENTER: 9963 if (event.hasNoModifiers()) { 9964 /* 9965 * If there is a click listener, just call through to 9966 * super, which will invoke it. 9967 * 9968 * If there isn't a click listener, try to show the soft 9969 * input method. (It will also 9970 * call performClick(), but that won't do anything in 9971 * this case.) 9972 */ 9973 if (!hasOnClickListeners()) { 9974 if (mMovement != null && mText instanceof Editable 9975 && mLayout != null && onCheckIsTextEditor()) { 9976 InputMethodManager imm = getInputMethodManager(); 9977 viewClicked(imm); 9978 if (imm != null && getShowSoftInputOnFocus()) { 9979 imm.showSoftInput(this, 0); 9980 } 9981 } 9982 } 9983 } 9984 return super.onKeyUp(keyCode, event); 9985 9986 case KeyEvent.KEYCODE_ENTER: 9987 case KeyEvent.KEYCODE_NUMPAD_ENTER: 9988 if (event.hasNoModifiers()) { 9989 if (mEditor != null && mEditor.mInputContentType != null 9990 && mEditor.mInputContentType.onEditorActionListener != null 9991 && mEditor.mInputContentType.enterDown) { 9992 mEditor.mInputContentType.enterDown = false; 9993 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 9994 this, getActionIdForEnterEvent(), event)) { 9995 return true; 9996 } 9997 } 9998 9999 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 10000 || shouldAdvanceFocusOnEnter()) { 10001 /* 10002 * If there is a click listener, just call through to 10003 * super, which will invoke it. 10004 * 10005 * If there isn't a click listener, try to advance focus, 10006 * but still call through to super, which will reset the 10007 * pressed state and longpress state. (It will also 10008 * call performClick(), but that won't do anything in 10009 * this case.) 10010 */ 10011 if (!hasOnClickListeners()) { 10012 View v = focusSearch(FOCUS_DOWN); 10013 10014 if (v != null) { 10015 if (!v.requestFocus(FOCUS_DOWN)) { 10016 throw new IllegalStateException("focus search returned a view " 10017 + "that wasn't able to take focus!"); 10018 } 10019 10020 /* 10021 * Return true because we handled the key; super 10022 * will return false because there was no click 10023 * listener. 10024 */ 10025 super.onKeyUp(keyCode, event); 10026 return true; 10027 } else if ((event.getFlags() 10028 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 10029 // No target for next focus, but make sure the IME 10030 // if this came from it. 10031 InputMethodManager imm = getInputMethodManager(); 10032 if (imm != null) { 10033 imm.hideSoftInputFromView(this, 0); 10034 } 10035 } 10036 } 10037 } 10038 return super.onKeyUp(keyCode, event); 10039 } 10040 break; 10041 } 10042 10043 if (mEditor != null && mEditor.mKeyListener != null) { 10044 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) { 10045 return true; 10046 } 10047 } 10048 10049 if (mMovement != null && mLayout != null) { 10050 if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) { 10051 return true; 10052 } 10053 } 10054 10055 return super.onKeyUp(keyCode, event); 10056 } 10057 getActionIdForEnterEvent()10058 private int getActionIdForEnterEvent() { 10059 // If it's not single line, no action 10060 if (!isSingleLine()) { 10061 return EditorInfo.IME_NULL; 10062 } 10063 // Return the action that was specified for Enter 10064 return getImeOptions() & EditorInfo.IME_MASK_ACTION; 10065 } 10066 10067 @Override onCheckIsTextEditor()10068 public boolean onCheckIsTextEditor() { 10069 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 10070 } 10071 hasEditorInFocusSearchDirection(@ocusRealDirection int direction)10072 private boolean hasEditorInFocusSearchDirection(@FocusRealDirection int direction) { 10073 final View nextView = focusSearch(direction); 10074 return nextView != null && nextView.onCheckIsTextEditor(); 10075 } 10076 10077 @Override onCreateInputConnection(EditorInfo outAttrs)10078 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 10079 if (onCheckIsTextEditor() && isEnabled()) { 10080 mEditor.createInputMethodStateIfNeeded(); 10081 mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = 0; 10082 mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = 0; 10083 10084 outAttrs.inputType = getInputType(); 10085 if (mEditor.mInputContentType != null) { 10086 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 10087 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 10088 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 10089 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 10090 outAttrs.extras = mEditor.mInputContentType.extras; 10091 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales; 10092 } else { 10093 outAttrs.imeOptions = EditorInfo.IME_NULL; 10094 outAttrs.hintLocales = null; 10095 } 10096 if (hasEditorInFocusSearchDirection(FOCUS_DOWN)) { 10097 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 10098 } 10099 if (hasEditorInFocusSearchDirection(FOCUS_UP)) { 10100 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 10101 } 10102 if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION) 10103 == EditorInfo.IME_ACTION_UNSPECIFIED) { 10104 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 10105 // An action has not been set, but the enter key will move to 10106 // the next focus, so set the action to that. 10107 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 10108 } else { 10109 // An action has not been set, and there is no focus to move 10110 // to, so let's just supply a "done" action. 10111 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 10112 } 10113 if (!shouldAdvanceFocusOnEnter()) { 10114 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 10115 } 10116 } 10117 if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) { 10118 outAttrs.internalImeOptions |= EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT; 10119 } 10120 if (isMultilineInputType(outAttrs.inputType)) { 10121 // Multi-line text editors should always show an enter key. 10122 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 10123 } 10124 outAttrs.hintText = mHint; 10125 outAttrs.targetInputMethodUser = mTextOperationUser; 10126 if (mText instanceof Editable) { 10127 InputConnection ic = new EditableInputConnection(this); 10128 outAttrs.initialSelStart = getSelectionStart(); 10129 outAttrs.initialSelEnd = getSelectionEnd(); 10130 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 10131 outAttrs.setInitialSurroundingText(mText); 10132 outAttrs.contentMimeTypes = getReceiveContentMimeTypes(); 10133 if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()) { 10134 boolean handwritingEnabled = isAutoHandwritingEnabled(); 10135 outAttrs.setStylusHandwritingEnabled(handwritingEnabled); 10136 // AndroidX Core library 1.13.0 introduced 10137 // EditorInfoCompat#setStylusHandwritingEnabled and 10138 // EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the 10139 // EditorInfo extras bundle. These methods do not set or check the Android V 10140 // property since the Android V SDK was not yet available. In order for 10141 // EditorInfoCompat#isStylusHandwritingEnabled to return the correct value for 10142 // EditorInfo created by Android V TextView, the extras bundle value is also set 10143 // here. 10144 if (outAttrs.extras == null) { 10145 outAttrs.extras = new Bundle(); 10146 } 10147 outAttrs.extras.putBoolean( 10148 STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY, handwritingEnabled); 10149 } 10150 if (android.view.inputmethod.Flags.writingTools()) { 10151 // default to same behavior as isSuggestionsEnabled(). 10152 outAttrs.setWritingToolsEnabled(isSuggestionsEnabled()); 10153 } 10154 ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>(); 10155 gestures.add(SelectGesture.class); 10156 gestures.add(SelectRangeGesture.class); 10157 gestures.add(DeleteGesture.class); 10158 gestures.add(DeleteRangeGesture.class); 10159 gestures.add(InsertGesture.class); 10160 gestures.add(RemoveSpaceGesture.class); 10161 gestures.add(JoinOrSplitGesture.class); 10162 gestures.add(InsertModeGesture.class); 10163 outAttrs.setSupportedHandwritingGestures(gestures); 10164 10165 Set<Class<? extends PreviewableHandwritingGesture>> previews = new ArraySet<>(); 10166 previews.add(SelectGesture.class); 10167 previews.add(SelectRangeGesture.class); 10168 previews.add(DeleteGesture.class); 10169 previews.add(DeleteRangeGesture.class); 10170 outAttrs.setSupportedHandwritingGesturePreviews(previews); 10171 10172 return ic; 10173 } 10174 } 10175 return null; 10176 } 10177 10178 /** 10179 * Called back by the system to handle {@link InputConnection#requestCursorUpdates(int, int)}. 10180 * 10181 * @param cursorUpdateMode modes defined in {@link InputConnection.CursorUpdateMode}. 10182 * @param cursorUpdateFilter modes defined in {@link InputConnection.CursorUpdateFilter}. 10183 * 10184 * @hide 10185 */ onRequestCursorUpdatesInternal( @nputConnection.CursorUpdateMode int cursorUpdateMode, @InputConnection.CursorUpdateFilter int cursorUpdateFilter)10186 public void onRequestCursorUpdatesInternal( 10187 @InputConnection.CursorUpdateMode int cursorUpdateMode, 10188 @InputConnection.CursorUpdateFilter int cursorUpdateFilter) { 10189 mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = cursorUpdateMode; 10190 mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = cursorUpdateFilter; 10191 if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) == 0) { 10192 return; 10193 } 10194 if (isInLayout()) { 10195 // In this case, the view hierarchy is currently undergoing a layout pass. 10196 // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout 10197 // pass is finished. 10198 } else { 10199 // This will schedule a layout pass of the view tree, and the layout event 10200 // eventually triggers IMM#updateCursorAnchorInfo. 10201 requestLayout(); 10202 } 10203 } 10204 10205 /** 10206 * If this TextView contains editable content, extract a portion of it 10207 * based on the information in <var>request</var> in to <var>outText</var>. 10208 * @return Returns true if the text was successfully extracted, else false. 10209 */ extractText(ExtractedTextRequest request, ExtractedText outText)10210 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 10211 createEditorIfNeeded(); 10212 return mEditor.extractText(request, outText); 10213 } 10214 10215 /** 10216 * This is used to remove all style-impacting spans from text before new 10217 * extracted text is being replaced into it, so that we don't have any 10218 * lingering spans applied during the replace. 10219 */ removeParcelableSpans(Spannable spannable, int start, int end)10220 static void removeParcelableSpans(Spannable spannable, int start, int end) { 10221 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 10222 int i = spans.length; 10223 while (i > 0) { 10224 i--; 10225 spannable.removeSpan(spans[i]); 10226 } 10227 } 10228 10229 /** 10230 * Apply to this text view the given extracted text, as previously 10231 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 10232 */ setExtractedText(ExtractedText text)10233 public void setExtractedText(ExtractedText text) { 10234 Editable content = getEditableText(); 10235 if (text.text != null) { 10236 if (content == null) { 10237 setText(text.text, TextView.BufferType.EDITABLE); 10238 } else { 10239 int start = 0; 10240 int end = content.length(); 10241 10242 if (text.partialStartOffset >= 0) { 10243 final int N = content.length(); 10244 start = text.partialStartOffset; 10245 if (start > N) start = N; 10246 end = text.partialEndOffset; 10247 if (end > N) end = N; 10248 } 10249 10250 removeParcelableSpans(content, start, end); 10251 if (TextUtils.equals(content.subSequence(start, end), text.text)) { 10252 if (text.text instanceof Spanned) { 10253 // OK to copy spans only. 10254 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start, 10255 Object.class, content, start); 10256 } 10257 } else { 10258 content.replace(start, end, text.text); 10259 } 10260 } 10261 } 10262 10263 // Now set the selection position... make sure it is in range, to 10264 // avoid crashes. If this is a partial update, it is possible that 10265 // the underlying text may have changed, causing us problems here. 10266 // Also we just don't want to trust clients to do the right thing. 10267 Spannable sp = (Spannable) getText(); 10268 final int N = sp.length(); 10269 int start = text.selectionStart; 10270 if (start < 0) { 10271 start = 0; 10272 } else if (start > N) { 10273 start = N; 10274 } 10275 int end = text.selectionEnd; 10276 if (end < 0) { 10277 end = 0; 10278 } else if (end > N) { 10279 end = N; 10280 } 10281 Selection.setSelection(sp, start, end); 10282 10283 // Finally, update the selection mode. 10284 if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) { 10285 MetaKeyKeyListener.startSelecting(this, sp); 10286 } else { 10287 MetaKeyKeyListener.stopSelecting(this, sp); 10288 } 10289 10290 setHintInternal(text.hint); 10291 } 10292 10293 /** 10294 * @hide 10295 */ setExtracting(ExtractedTextRequest req)10296 public void setExtracting(ExtractedTextRequest req) { 10297 if (mEditor.mInputMethodState != null) { 10298 mEditor.mInputMethodState.mExtractedTextRequest = req; 10299 } 10300 // This would stop a possible selection mode, but no such mode is started in case 10301 // extracted mode will start. Some text is selected though, and will trigger an action mode 10302 // in the extracted view. 10303 mEditor.hideCursorAndSpanControllers(); 10304 stopTextActionMode(); 10305 if (mEditor.mSelectionModifierCursorController != null) { 10306 mEditor.mSelectionModifierCursorController.resetTouchOffsets(); 10307 } 10308 } 10309 10310 /** 10311 * Called by the framework in response to a text completion from 10312 * the current input method, provided by it calling 10313 * {@link InputConnection#commitCompletion 10314 * InputConnection.commitCompletion()}. The default implementation does 10315 * nothing; text views that are supporting auto-completion should override 10316 * this to do their desired behavior. 10317 * 10318 * @param text The auto complete text the user has selected. 10319 */ onCommitCompletion(CompletionInfo text)10320 public void onCommitCompletion(CompletionInfo text) { 10321 // intentionally empty 10322 } 10323 10324 /** 10325 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 10326 * dictionary) from the current input method, provided by it calling 10327 * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}. 10328 * The default implementation flashes the background of the corrected word to provide 10329 * feedback to the user. 10330 * 10331 * @param info The auto correct info about the text that was corrected. 10332 */ onCommitCorrection(CorrectionInfo info)10333 public void onCommitCorrection(CorrectionInfo info) { 10334 if (mEditor != null) mEditor.onCommitCorrection(info); 10335 } 10336 beginBatchEdit()10337 public void beginBatchEdit() { 10338 if (mEditor != null) mEditor.beginBatchEdit(); 10339 } 10340 endBatchEdit()10341 public void endBatchEdit() { 10342 if (mEditor != null) mEditor.endBatchEdit(); 10343 } 10344 10345 /** 10346 * Called by the framework in response to a request to begin a batch 10347 * of edit operations through a call to link {@link #beginBatchEdit()}. 10348 */ onBeginBatchEdit()10349 public void onBeginBatchEdit() { 10350 // intentionally empty 10351 } 10352 10353 /** 10354 * Called by the framework in response to a request to end a batch 10355 * of edit operations through a call to link {@link #endBatchEdit}. 10356 */ onEndBatchEdit()10357 public void onEndBatchEdit() { 10358 // intentionally empty 10359 } 10360 10361 /** @hide */ onPerformSpellCheck()10362 public void onPerformSpellCheck() { 10363 if (mEditor != null && mEditor.mSpellChecker != null) { 10364 mEditor.mSpellChecker.onPerformSpellCheck(); 10365 } 10366 } 10367 10368 /** 10369 * Called by the framework in response to a private command from the 10370 * current method, provided by it calling 10371 * {@link InputConnection#performPrivateCommand 10372 * InputConnection.performPrivateCommand()}. 10373 * 10374 * @param action The action name of the command. 10375 * @param data Any additional data for the command. This may be null. 10376 * @return Return true if you handled the command, else false. 10377 */ onPrivateIMECommand(String action, Bundle data)10378 public boolean onPrivateIMECommand(String action, Bundle data) { 10379 return false; 10380 } 10381 10382 /** 10383 * Return whether the text is transformed and has {@link OffsetMapping}. 10384 * @hide 10385 */ isOffsetMappingAvailable()10386 public boolean isOffsetMappingAvailable() { 10387 return mTransformation != null && mTransformed instanceof OffsetMapping; 10388 } 10389 10390 /** @hide */ previewHandwritingGesture( @onNull PreviewableHandwritingGesture gesture, @Nullable CancellationSignal cancellationSignal)10391 public boolean previewHandwritingGesture( 10392 @NonNull PreviewableHandwritingGesture gesture, 10393 @Nullable CancellationSignal cancellationSignal) { 10394 if (gesture instanceof SelectGesture) { 10395 performHandwritingSelectGesture((SelectGesture) gesture, /* isPreview= */ true); 10396 } else if (gesture instanceof SelectRangeGesture) { 10397 performHandwritingSelectRangeGesture( 10398 (SelectRangeGesture) gesture, /* isPreview= */ true); 10399 } else if (gesture instanceof DeleteGesture) { 10400 performHandwritingDeleteGesture((DeleteGesture) gesture, /* isPreview= */ true); 10401 } else if (gesture instanceof DeleteRangeGesture) { 10402 performHandwritingDeleteRangeGesture( 10403 (DeleteRangeGesture) gesture, /* isPreview= */ true); 10404 } else { 10405 return false; 10406 } 10407 if (cancellationSignal != null) { 10408 cancellationSignal.setOnCancelListener(this::clearGesturePreviewHighlight); 10409 } 10410 return true; 10411 } 10412 10413 /** @hide */ performHandwritingSelectGesture(@onNull SelectGesture gesture)10414 public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) { 10415 return performHandwritingSelectGesture(gesture, /* isPreview= */ false); 10416 } 10417 performHandwritingSelectGesture(@onNull SelectGesture gesture, boolean isPreview)10418 private int performHandwritingSelectGesture(@NonNull SelectGesture gesture, boolean isPreview) { 10419 if (isOffsetMappingAvailable()) { 10420 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10421 } 10422 int[] range = getRangeForRect( 10423 convertFromScreenToContentCoordinates(gesture.getSelectionArea()), 10424 gesture.getGranularity()); 10425 if (range == null) { 10426 return handleGestureFailure(gesture, isPreview); 10427 } 10428 return performHandwritingSelectGesture(range, isPreview); 10429 } 10430 performHandwritingSelectGesture(int[] range, boolean isPreview)10431 private int performHandwritingSelectGesture(int[] range, boolean isPreview) { 10432 if (isPreview) { 10433 setSelectGesturePreviewHighlight(range[0], range[1]); 10434 } else { 10435 Selection.setSelection(getEditableText(), range[0], range[1]); 10436 mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false); 10437 } 10438 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10439 } 10440 10441 /** @hide */ performHandwritingSelectRangeGesture(@onNull SelectRangeGesture gesture)10442 public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) { 10443 return performHandwritingSelectRangeGesture(gesture, /* isPreview= */ false); 10444 } 10445 performHandwritingSelectRangeGesture( @onNull SelectRangeGesture gesture, boolean isPreview)10446 private int performHandwritingSelectRangeGesture( 10447 @NonNull SelectRangeGesture gesture, boolean isPreview) { 10448 if (isOffsetMappingAvailable()) { 10449 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10450 } 10451 int[] startRange = getRangeForRect( 10452 convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()), 10453 gesture.getGranularity()); 10454 if (startRange == null) { 10455 return handleGestureFailure(gesture, isPreview); 10456 } 10457 int[] endRange = getRangeForRect( 10458 convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()), 10459 gesture.getGranularity()); 10460 if (endRange == null) { 10461 return handleGestureFailure(gesture, isPreview); 10462 } 10463 int[] range = new int[] { 10464 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1]) 10465 }; 10466 return performHandwritingSelectGesture(range, isPreview); 10467 } 10468 10469 /** @hide */ performHandwritingDeleteGesture(@onNull DeleteGesture gesture)10470 public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) { 10471 return performHandwritingDeleteGesture(gesture, /* isPreview= */ false); 10472 } 10473 performHandwritingDeleteGesture(@onNull DeleteGesture gesture, boolean isPreview)10474 private int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture, boolean isPreview) { 10475 if (isOffsetMappingAvailable()) { 10476 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10477 } 10478 int[] range = getRangeForRect( 10479 convertFromScreenToContentCoordinates(gesture.getDeletionArea()), 10480 gesture.getGranularity()); 10481 if (range == null) { 10482 return handleGestureFailure(gesture, isPreview); 10483 } 10484 return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview); 10485 } 10486 performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview)10487 private int performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview) { 10488 if (isPreview) { 10489 setDeleteGesturePreviewHighlight(range[0], range[1]); 10490 } else { 10491 if (granularity == HandwritingGesture.GRANULARITY_WORD) { 10492 range = adjustHandwritingDeleteGestureRange(range); 10493 } 10494 10495 Selection.setSelection(getEditableText(), range[0]); 10496 getEditableText().delete(range[0], range[1]); 10497 } 10498 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10499 } 10500 10501 /** @hide */ performHandwritingDeleteRangeGesture(@onNull DeleteRangeGesture gesture)10502 public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) { 10503 return performHandwritingDeleteRangeGesture(gesture, /* isPreview= */ false); 10504 } 10505 performHandwritingDeleteRangeGesture( @onNull DeleteRangeGesture gesture, boolean isPreview)10506 private int performHandwritingDeleteRangeGesture( 10507 @NonNull DeleteRangeGesture gesture, boolean isPreview) { 10508 if (isOffsetMappingAvailable()) { 10509 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10510 } 10511 int[] startRange = getRangeForRect( 10512 convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()), 10513 gesture.getGranularity()); 10514 if (startRange == null) { 10515 return handleGestureFailure(gesture, isPreview); 10516 } 10517 int[] endRange = getRangeForRect( 10518 convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()), 10519 gesture.getGranularity()); 10520 if (endRange == null) { 10521 return handleGestureFailure(gesture, isPreview); 10522 } 10523 int[] range = new int[] { 10524 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1]) 10525 }; 10526 return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview); 10527 } 10528 adjustHandwritingDeleteGestureRange(int[] range)10529 private int[] adjustHandwritingDeleteGestureRange(int[] range) { 10530 // For handwriting delete gestures with word granularity, adjust the start and end offsets 10531 // to remove extra whitespace around the deleted text. 10532 10533 int start = range[0]; 10534 int end = range[1]; 10535 10536 // If the deleted text is at the start of the text, the behavior is the same as the case 10537 // where the deleted text follows a new line character. 10538 int codePointBeforeStart = start > 0 10539 ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT; 10540 // If the deleted text is at the end of the text, the behavior is the same as the case where 10541 // the deleted text precedes a new line character. 10542 int codePointAtEnd = end < mText.length() 10543 ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT; 10544 10545 if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart) 10546 && (TextUtils.isWhitespace(codePointAtEnd) 10547 || TextUtils.isPunctuation(codePointAtEnd))) { 10548 // Remove whitespace (except new lines) before the deleted text, in these cases: 10549 // - There is whitespace following the deleted text 10550 // e.g. "one [deleted] three" -> "one | three" -> "one| three" 10551 // - There is punctuation following the deleted text 10552 // e.g. "one [deleted]!" -> "one |!" -> "one|!" 10553 // - There is a new line following the deleted text 10554 // e.g. "one [deleted]\n" -> "one |\n" -> "one|\n" 10555 // - The deleted text is at the end of the text 10556 // e.g. "one [deleted]" -> "one |" -> "one|" 10557 // (The pipe | indicates the cursor position.) 10558 do { 10559 start -= Character.charCount(codePointBeforeStart); 10560 if (start == 0) break; 10561 codePointBeforeStart = Character.codePointBefore(mText, start); 10562 } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)); 10563 return new int[] {start, end}; 10564 } 10565 10566 if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd) 10567 && (TextUtils.isWhitespace(codePointBeforeStart) 10568 || TextUtils.isPunctuation(codePointBeforeStart))) { 10569 // Remove whitespace (except new lines) after the deleted text, in these cases: 10570 // - There is punctuation preceding the deleted text 10571 // e.g. "([deleted] two)" -> "(| two)" -> "(|two)" 10572 // - There is a new line preceding the deleted text 10573 // e.g. "\n[deleted] two" -> "\n| two" -> "\n|two" 10574 // - The deleted text is at the start of the text 10575 // e.g. "[deleted] two" -> "| two" -> "|two" 10576 // (The pipe | indicates the cursor position.) 10577 do { 10578 end += Character.charCount(codePointAtEnd); 10579 if (end == mText.length()) break; 10580 codePointAtEnd = Character.codePointAt(mText, end); 10581 } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)); 10582 return new int[] {start, end}; 10583 } 10584 10585 // Return the original range. 10586 return range; 10587 } 10588 10589 /** @hide */ 10590 public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) { 10591 if (isOffsetMappingAvailable()) { 10592 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10593 } 10594 PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint()); 10595 int line = getLineForHandwritingGesture(point); 10596 if (line == -1) { 10597 return handleGestureFailure(gesture); 10598 } 10599 int offset = mLayout.getOffsetForHorizontal(line, point.x); 10600 String textToInsert = gesture.getTextToInsert(); 10601 return tryInsertTextForHandwritingGesture(offset, textToInsert, gesture); 10602 // TODO(b/243980426): Insert extra spaces if necessary. 10603 } 10604 10605 /** @hide */ 10606 public int performHandwritingRemoveSpaceGesture(@NonNull RemoveSpaceGesture gesture) { 10607 if (isOffsetMappingAvailable()) { 10608 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10609 } 10610 PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint()); 10611 PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint()); 10612 10613 // The operation should be applied to the first line of text containing one of the points. 10614 int startPointLine = getLineForHandwritingGesture(startPoint); 10615 int endPointLine = getLineForHandwritingGesture(endPoint); 10616 int line; 10617 if (startPointLine == -1) { 10618 if (endPointLine == -1) { 10619 return handleGestureFailure(gesture); 10620 } 10621 line = endPointLine; 10622 } else { 10623 line = (endPointLine == -1) ? startPointLine : Math.min(startPointLine, endPointLine); 10624 } 10625 10626 // The operation should be applied to all characters touched by the line joining the points. 10627 float lineVerticalCenter = (mLayout.getLineTop(line) 10628 + mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) / 2f; 10629 // Create a rectangle which is +/-0.1f around the line's vertical center, so that the 10630 // rectangle doesn't touch the line above or below. (The line height is at least 1f.) 10631 RectF area = new RectF( 10632 Math.min(startPoint.x, endPoint.x), 10633 lineVerticalCenter + 0.1f, 10634 Math.max(startPoint.x, endPoint.x), 10635 lineVerticalCenter - 0.1f); 10636 int[] range = mLayout.getRangeForRect( 10637 area, new GraphemeClusterSegmentFinder(mText, mTextPaint), 10638 Layout.INCLUSION_STRATEGY_ANY_OVERLAP); 10639 if (range == null) { 10640 return handleGestureFailure(gesture); 10641 } 10642 int startOffset = range[0]; 10643 int endOffset = range[1]; 10644 // TODO(b/247557062): This doesn't handle bidirectional text correctly. 10645 10646 Pattern whitespacePattern = getWhitespacePattern(); 10647 Matcher matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset)); 10648 int lastRemoveOffset = -1; 10649 while (matcher.find()) { 10650 lastRemoveOffset = startOffset + matcher.start(); 10651 getEditableText().delete(lastRemoveOffset, startOffset + matcher.end()); 10652 startOffset = lastRemoveOffset; 10653 endOffset -= matcher.end() - matcher.start(); 10654 if (startOffset == endOffset) { 10655 break; 10656 } 10657 matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset)); 10658 } 10659 if (lastRemoveOffset == -1) { 10660 return handleGestureFailure(gesture); 10661 } 10662 Selection.setSelection(getEditableText(), lastRemoveOffset); 10663 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10664 } 10665 10666 /** @hide */ 10667 public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) { 10668 if (isOffsetMappingAvailable()) { 10669 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10670 } 10671 PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint()); 10672 10673 int line = getLineForHandwritingGesture(point); 10674 if (line == -1) { 10675 return handleGestureFailure(gesture); 10676 } 10677 10678 int startOffset = mLayout.getOffsetForHorizontal(line, point.x); 10679 if (mLayout.isLevelBoundary(startOffset)) { 10680 // Gesture at level boundaries is not supported. 10681 return handleGestureFailure(gesture); 10682 } 10683 10684 int endOffset = startOffset; 10685 while (startOffset > 0) { 10686 int codePointBeforeStart = Character.codePointBefore(mText, startOffset); 10687 if (!TextUtils.isWhitespace(codePointBeforeStart)) { 10688 break; 10689 } 10690 startOffset -= Character.charCount(codePointBeforeStart); 10691 } 10692 while (endOffset < mText.length()) { 10693 int codePointAtEnd = Character.codePointAt(mText, endOffset); 10694 if (!TextUtils.isWhitespace(codePointAtEnd)) { 10695 break; 10696 } 10697 endOffset += Character.charCount(codePointAtEnd); 10698 } 10699 if (startOffset < endOffset) { 10700 Selection.setSelection(getEditableText(), startOffset); 10701 getEditableText().delete(startOffset, endOffset); 10702 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10703 } else { 10704 // No whitespace found, so insert a space. 10705 return tryInsertTextForHandwritingGesture(startOffset, " ", gesture); 10706 } 10707 } 10708 10709 /** @hide */ 10710 public int performHandwritingInsertModeGesture(@NonNull InsertModeGesture gesture) { 10711 final PointF insertPoint = 10712 convertFromScreenToContentCoordinates(gesture.getInsertionPoint()); 10713 final int line = getLineForHandwritingGesture(insertPoint); 10714 final CancellationSignal cancellationSignal = gesture.getCancellationSignal(); 10715 10716 // If no cancellationSignal is provided, don't enter the insert mode. 10717 if (line == -1 || cancellationSignal == null) { 10718 return handleGestureFailure(gesture); 10719 } 10720 10721 final int offset = mLayout.getOffsetForHorizontal(line, insertPoint.x); 10722 10723 if (!mEditor.enterInsertMode(offset)) { 10724 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10725 } 10726 cancellationSignal.setOnCancelListener(() -> mEditor.exitInsertMode()); 10727 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10728 } 10729 10730 private int handleGestureFailure(HandwritingGesture gesture) { 10731 return handleGestureFailure(gesture, /* isPreview= */ false); 10732 } 10733 10734 private int handleGestureFailure(HandwritingGesture gesture, boolean isPreview) { 10735 clearGesturePreviewHighlight(); 10736 if (!isPreview && !TextUtils.isEmpty(gesture.getFallbackText())) { 10737 getEditableText() 10738 .replace(getSelectionStart(), getSelectionEnd(), gesture.getFallbackText()); 10739 return InputConnection.HANDWRITING_GESTURE_RESULT_FALLBACK; 10740 } 10741 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10742 } 10743 10744 /** 10745 * Returns the closest line such that the point is either inside the line bounds or within 10746 * {@link ViewConfiguration#getScaledHandwritingGestureLineMargin} of the line bounds. Returns 10747 * -1 if the point is not within the margin of any line bounds. 10748 */ 10749 private int getLineForHandwritingGesture(PointF point) { 10750 int line = mLayout.getLineForVertical((int) point.y); 10751 int lineMargin = ViewConfiguration.get(mContext).getScaledHandwritingGestureLineMargin(); 10752 if (line < mLayout.getLineCount() - 1 10753 && point.y > mLayout.getLineBottom(line) - lineMargin 10754 && point.y 10755 > (mLayout.getLineBottom(line, false) + mLayout.getLineBottom(line)) / 2f) { 10756 // If a point is in the space between line i and line (i + 1), Layout#getLineForVertical 10757 // returns i. If the point is within lineMargin of line (i + 1), and closer to line 10758 // (i + 1) than line i, then the gesture operation should be applied to line (i + 1). 10759 line++; 10760 } else if (point.y < mLayout.getLineTop(line) - lineMargin 10761 || point.y 10762 > mLayout.getLineBottom(line, /* includeLineSpacing= */ false) 10763 + lineMargin) { 10764 // The point is not within lineMargin of a line. 10765 return -1; 10766 } 10767 if (point.x < -lineMargin || point.x > mLayout.getWidth() + lineMargin) { 10768 // The point is not within lineMargin of a line. 10769 return -1; 10770 } 10771 return line; 10772 } 10773 10774 @Nullable 10775 private int[] getRangeForRect(@NonNull RectF area, int granularity) { 10776 SegmentFinder segmentFinder; 10777 if (granularity == HandwritingGesture.GRANULARITY_WORD) { 10778 WordIterator wordIterator = getWordIterator(); 10779 wordIterator.setCharSequence(mText, 0, mText.length()); 10780 segmentFinder = new WordSegmentFinder(mText, wordIterator); 10781 } else { 10782 segmentFinder = new GraphemeClusterSegmentFinder(mText, mTextPaint); 10783 } 10784 10785 return mLayout.getRangeForRect( 10786 area, segmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER); 10787 } 10788 10789 private int tryInsertTextForHandwritingGesture( 10790 int offset, String textToInsert, HandwritingGesture gesture) { 10791 // A temporary cursor span is placed at the insertion offset. The span will be pushed 10792 // forward when text is inserted, then the real cursor can be placed after the inserted 10793 // text. A temporary cursor span is used in order to avoid modifying the real selection span 10794 // in the case that the text is filtered out. 10795 Editable editableText = getEditableText(); 10796 if (mTempCursor == null) { 10797 mTempCursor = new NoCopySpan.Concrete(); 10798 } 10799 editableText.setSpan(mTempCursor, offset, offset, Spanned.SPAN_POINT_POINT); 10800 10801 editableText.insert(offset, textToInsert); 10802 10803 int newOffset = editableText.getSpanStart(mTempCursor); 10804 editableText.removeSpan(mTempCursor); 10805 if (newOffset == offset) { 10806 // The inserted text was filtered out. 10807 return handleGestureFailure(gesture); 10808 } else { 10809 // Place the cursor after the inserted text. 10810 Selection.setSelection(editableText, newOffset); 10811 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10812 } 10813 } 10814 10815 private Pattern getWhitespacePattern() { 10816 if (mWhitespacePattern == null) { 10817 mWhitespacePattern = Pattern.compile("\\s+"); 10818 } 10819 return mWhitespacePattern; 10820 } 10821 10822 /** @hide */ 10823 @VisibleForTesting 10824 @UnsupportedAppUsage 10825 public void nullLayouts() { 10826 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 10827 mSavedLayout = (BoringLayout) mLayout; 10828 } 10829 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 10830 mSavedHintLayout = (BoringLayout) mHintLayout; 10831 } 10832 10833 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 10834 10835 mBoring = mHintBoring = null; 10836 10837 // Since it depends on the value of mLayout 10838 if (mEditor != null) mEditor.prepareCursorControllers(); 10839 } 10840 10841 /** 10842 * Make a new Layout based on the already-measured size of the view, 10843 * on the assumption that it was measured correctly at some point. 10844 */ 10845 @UnsupportedAppUsage 10846 private void assumeLayout() { 10847 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10848 10849 if (width < 1) { 10850 width = 0; 10851 } 10852 10853 int physicalWidth = width; 10854 10855 if (mHorizontallyScrolling) { 10856 width = VERY_WIDE; 10857 } 10858 10859 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 10860 physicalWidth, false); 10861 } 10862 10863 @UnsupportedAppUsage 10864 private Layout.Alignment getLayoutAlignment() { 10865 Layout.Alignment alignment; 10866 switch (getTextAlignment()) { 10867 case TEXT_ALIGNMENT_GRAVITY: 10868 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 10869 case Gravity.START: 10870 alignment = Layout.Alignment.ALIGN_NORMAL; 10871 break; 10872 case Gravity.END: 10873 alignment = Layout.Alignment.ALIGN_OPPOSITE; 10874 break; 10875 case Gravity.LEFT: 10876 alignment = Layout.Alignment.ALIGN_LEFT; 10877 break; 10878 case Gravity.RIGHT: 10879 alignment = Layout.Alignment.ALIGN_RIGHT; 10880 break; 10881 case Gravity.CENTER_HORIZONTAL: 10882 alignment = Layout.Alignment.ALIGN_CENTER; 10883 break; 10884 default: 10885 alignment = Layout.Alignment.ALIGN_NORMAL; 10886 break; 10887 } 10888 break; 10889 case TEXT_ALIGNMENT_TEXT_START: 10890 alignment = Layout.Alignment.ALIGN_NORMAL; 10891 break; 10892 case TEXT_ALIGNMENT_TEXT_END: 10893 alignment = Layout.Alignment.ALIGN_OPPOSITE; 10894 break; 10895 case TEXT_ALIGNMENT_CENTER: 10896 alignment = Layout.Alignment.ALIGN_CENTER; 10897 break; 10898 case TEXT_ALIGNMENT_VIEW_START: 10899 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 10900 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 10901 break; 10902 case TEXT_ALIGNMENT_VIEW_END: 10903 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 10904 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 10905 break; 10906 case TEXT_ALIGNMENT_INHERIT: 10907 // This should never happen as we have already resolved the text alignment 10908 // but better safe than sorry so we just fall through 10909 default: 10910 alignment = Layout.Alignment.ALIGN_NORMAL; 10911 break; 10912 } 10913 return alignment; 10914 } 10915 10916 private Paint.FontMetrics getResolvedMinimumFontMetrics() { 10917 if (mMinimumFontMetrics != null) { 10918 return mMinimumFontMetrics; 10919 } 10920 if (!mUseLocalePreferredLineHeightForMinimum) { 10921 return null; 10922 } 10923 10924 if (mLocalePreferredFontMetrics == null) { 10925 mLocalePreferredFontMetrics = new Paint.FontMetrics(); 10926 } 10927 mTextPaint.getFontMetricsForLocale(mLocalePreferredFontMetrics); 10928 return mLocalePreferredFontMetrics; 10929 } 10930 10931 /** 10932 * The width passed in is now the desired layout width, 10933 * not the full view width with padding. 10934 * {@hide} 10935 */ 10936 @VisibleForTesting 10937 @UnsupportedAppUsage 10938 public void makeNewLayout(int wantWidth, int hintWidth, 10939 BoringLayout.Metrics boring, 10940 BoringLayout.Metrics hintBoring, 10941 int ellipsisWidth, boolean bringIntoView) { 10942 stopMarquee(); 10943 10944 // Update "old" cached values 10945 mOldMaximum = mMaximum; 10946 mOldMaxMode = mMaxMode; 10947 10948 mHighlightPathBogus = true; 10949 mHighlightPathsBogus = true; 10950 10951 if (wantWidth < 0) { 10952 wantWidth = 0; 10953 } 10954 if (hintWidth < 0) { 10955 hintWidth = 0; 10956 } 10957 10958 Layout.Alignment alignment = getLayoutAlignment(); 10959 final boolean testDirChange = mSingleLine && mLayout != null 10960 && (alignment == Layout.Alignment.ALIGN_NORMAL 10961 || alignment == Layout.Alignment.ALIGN_OPPOSITE); 10962 int oldDir = 0; 10963 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 10964 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 10965 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE 10966 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 10967 TruncateAt effectiveEllipsize = mEllipsize; 10968 if (mEllipsize == TruncateAt.MARQUEE 10969 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 10970 effectiveEllipsize = TruncateAt.END_SMALL; 10971 } 10972 10973 if (mTextDir == null) { 10974 mTextDir = getTextDirectionHeuristic(); 10975 } 10976 10977 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 10978 effectiveEllipsize, effectiveEllipsize == mEllipsize); 10979 if (switchEllipsize) { 10980 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE 10981 ? TruncateAt.END : TruncateAt.MARQUEE; 10982 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 10983 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 10984 } 10985 10986 shouldEllipsize = mEllipsize != null; 10987 mHintLayout = null; 10988 10989 if (mHint != null) { 10990 if (shouldEllipsize) hintWidth = wantWidth; 10991 10992 if (hintBoring == UNKNOWN_BORING) { 10993 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 10994 isFallbackLineSpacingForBoringLayout(), 10995 getResolvedMinimumFontMetrics(), mHintBoring); 10996 10997 if (hintBoring != null) { 10998 mHintBoring = hintBoring; 10999 } 11000 } 11001 11002 if (hintBoring != null) { 11003 if (hintBoring.width <= hintWidth 11004 && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 11005 if (mSavedHintLayout != null) { 11006 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 11007 hintWidth, alignment, mSpacingMult, mSpacingAdd, 11008 hintBoring, mIncludePad); 11009 } else { 11010 mHintLayout = BoringLayout.make(mHint, mTextPaint, 11011 hintWidth, alignment, mSpacingMult, mSpacingAdd, 11012 hintBoring, mIncludePad); 11013 } 11014 11015 mSavedHintLayout = (BoringLayout) mHintLayout; 11016 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 11017 if (mSavedHintLayout != null) { 11018 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 11019 hintWidth, alignment, mSpacingMult, mSpacingAdd, 11020 hintBoring, mIncludePad, mEllipsize, 11021 ellipsisWidth); 11022 } else { 11023 mHintLayout = BoringLayout.make(mHint, mTextPaint, 11024 hintWidth, alignment, mSpacingMult, mSpacingAdd, 11025 hintBoring, mIncludePad, mEllipsize, 11026 ellipsisWidth); 11027 } 11028 } 11029 } 11030 // TODO: code duplication with makeSingleLayout() 11031 if (mHintLayout == null) { 11032 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, 11033 mHint.length(), mTextPaint, hintWidth) 11034 .setAlignment(alignment) 11035 .setTextDirection(mTextDir) 11036 .setLineSpacing(mSpacingAdd, mSpacingMult) 11037 .setIncludePad(mIncludePad) 11038 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 11039 .setBreakStrategy(mBreakStrategy) 11040 .setHyphenationFrequency(mHyphenationFrequency) 11041 .setJustificationMode(mJustificationMode) 11042 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 11043 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( 11044 mLineBreakStyle, mLineBreakWordStyle)) 11045 .setUseBoundsForWidth(mUseBoundsForWidth) 11046 .setMinimumFontMetrics(getResolvedMinimumFontMetrics()); 11047 11048 if (shouldEllipsize) { 11049 builder.setEllipsize(mEllipsize) 11050 .setEllipsizedWidth(ellipsisWidth); 11051 } 11052 mHintLayout = builder.build(); 11053 } 11054 } 11055 11056 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 11057 registerForPreDraw(); 11058 } 11059 11060 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 11061 if (!compressText(ellipsisWidth)) { 11062 final int height = mLayoutParams.height; 11063 // If the size of the view does not depend on the size of the text, try to 11064 // start the marquee immediately 11065 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 11066 startMarquee(); 11067 } else { 11068 // Defer the start of the marquee until we know our width (see setFrame()) 11069 mRestartMarquee = true; 11070 } 11071 } 11072 } 11073 11074 // CursorControllers need a non-null mLayout 11075 if (mEditor != null) mEditor.prepareCursorControllers(); 11076 } 11077 11078 /** 11079 * Returns true if DynamicLayout is required 11080 * 11081 * @hide 11082 */ 11083 @VisibleForTesting 11084 public boolean useDynamicLayout() { 11085 return isTextSelectable() || (mSpannable != null && mPrecomputed == null); 11086 } 11087 11088 /** 11089 * @hide 11090 */ 11091 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 11092 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 11093 boolean useSaved) { 11094 Layout result = null; 11095 if (useDynamicLayout()) { 11096 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint, 11097 wantWidth) 11098 .setDisplayText(mTransformed) 11099 .setAlignment(alignment) 11100 .setTextDirection(mTextDir) 11101 .setLineSpacing(mSpacingAdd, mSpacingMult) 11102 .setIncludePad(mIncludePad) 11103 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 11104 .setBreakStrategy(mBreakStrategy) 11105 .setHyphenationFrequency(mHyphenationFrequency) 11106 .setJustificationMode(mJustificationMode) 11107 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( 11108 mLineBreakStyle, mLineBreakWordStyle)) 11109 .setUseBoundsForWidth(mUseBoundsForWidth) 11110 .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null) 11111 .setEllipsizedWidth(ellipsisWidth) 11112 .setMinimumFontMetrics(getResolvedMinimumFontMetrics()); 11113 result = builder.build(); 11114 } else { 11115 if (boring == UNKNOWN_BORING) { 11116 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, 11117 isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(), 11118 mBoring); 11119 if (boring != null) { 11120 mBoring = boring; 11121 } 11122 } 11123 11124 if (boring != null) { 11125 if (boring.width <= wantWidth 11126 && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 11127 if (useSaved && mSavedLayout != null) { 11128 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 11129 wantWidth, alignment, mSpacingMult, mSpacingAdd, 11130 boring, mIncludePad, null, wantWidth, 11131 isFallbackLineSpacingForBoringLayout(), 11132 mUseBoundsForWidth, getResolvedMinimumFontMetrics()); 11133 } else { 11134 result = new BoringLayout( 11135 mTransformed, 11136 mTextPaint, 11137 wantWidth, 11138 alignment, 11139 mSpacingMult, 11140 mSpacingAdd, 11141 mIncludePad, 11142 isFallbackLineSpacingForBoringLayout(), 11143 wantWidth, 11144 null, 11145 boring, 11146 mUseBoundsForWidth, 11147 mShiftDrawingOffsetForStartOverhang, 11148 getResolvedMinimumFontMetrics()); 11149 } 11150 11151 if (useSaved) { 11152 mSavedLayout = (BoringLayout) result; 11153 } 11154 } else if (shouldEllipsize && boring.width <= wantWidth) { 11155 if (useSaved && mSavedLayout != null) { 11156 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 11157 wantWidth, alignment, mSpacingMult, mSpacingAdd, 11158 boring, mIncludePad, effectiveEllipsize, 11159 ellipsisWidth, isFallbackLineSpacingForBoringLayout(), 11160 mUseBoundsForWidth, getResolvedMinimumFontMetrics()); 11161 } else { 11162 result = new BoringLayout( 11163 mTransformed, 11164 mTextPaint, 11165 wantWidth, 11166 alignment, 11167 mSpacingMult, 11168 mSpacingAdd, 11169 mIncludePad, 11170 isFallbackLineSpacingForBoringLayout(), 11171 ellipsisWidth, 11172 effectiveEllipsize, 11173 boring, 11174 mUseBoundsForWidth, 11175 mShiftDrawingOffsetForStartOverhang, 11176 getResolvedMinimumFontMetrics()); 11177 } 11178 } 11179 } 11180 } 11181 if (result == null) { 11182 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 11183 0, mTransformed.length(), mTextPaint, wantWidth) 11184 .setAlignment(alignment) 11185 .setTextDirection(mTextDir) 11186 .setLineSpacing(mSpacingAdd, mSpacingMult) 11187 .setIncludePad(mIncludePad) 11188 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 11189 .setBreakStrategy(mBreakStrategy) 11190 .setHyphenationFrequency(mHyphenationFrequency) 11191 .setJustificationMode(mJustificationMode) 11192 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 11193 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( 11194 mLineBreakStyle, mLineBreakWordStyle)) 11195 .setUseBoundsForWidth(mUseBoundsForWidth) 11196 .setMinimumFontMetrics(getResolvedMinimumFontMetrics()); 11197 if (shouldEllipsize) { 11198 builder.setEllipsize(effectiveEllipsize) 11199 .setEllipsizedWidth(ellipsisWidth); 11200 } 11201 result = builder.build(); 11202 } 11203 return result; 11204 } 11205 11206 @UnsupportedAppUsage 11207 private boolean compressText(float width) { 11208 if (isHardwareAccelerated()) return false; 11209 11210 // Only compress the text if it hasn't been compressed by the previous pass 11211 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX 11212 && mTextPaint.getTextScaleX() == 1.0f) { 11213 final float textWidth = mLayout.getLineWidth(0); 11214 final float overflow = (textWidth + 1.0f - width) / width; 11215 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 11216 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 11217 post(new Runnable() { 11218 public void run() { 11219 requestLayout(); 11220 } 11221 }); 11222 return true; 11223 } 11224 } 11225 11226 return false; 11227 } 11228 11229 private static int desired(Layout layout, boolean useBoundsForWidth) { 11230 int n = layout.getLineCount(); 11231 CharSequence text = layout.getText(); 11232 float max = 0; 11233 11234 // if any line was wrapped, we can't use it. 11235 // but it's ok for the last line not to have a newline 11236 11237 for (int i = 0; i < n - 1; i++) { 11238 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') { 11239 return -1; 11240 } 11241 } 11242 11243 for (int i = 0; i < n; i++) { 11244 max = Math.max(max, layout.getLineMax(i)); 11245 } 11246 11247 if (useBoundsForWidth) { 11248 max = Math.max(max, layout.computeDrawingBoundingBox().width()); 11249 } 11250 11251 return (int) Math.ceil(max); 11252 } 11253 11254 /** 11255 * Set whether the TextView includes extra top and bottom padding to make 11256 * room for accents that go above the normal ascent and descent. 11257 * The default is true. 11258 * 11259 * @see #getIncludeFontPadding() 11260 * 11261 * @attr ref android.R.styleable#TextView_includeFontPadding 11262 */ 11263 public void setIncludeFontPadding(boolean includepad) { 11264 if (mIncludePad != includepad) { 11265 mIncludePad = includepad; 11266 11267 if (mLayout != null) { 11268 nullLayouts(); 11269 requestLayout(); 11270 invalidate(); 11271 } 11272 } 11273 } 11274 11275 /** 11276 * Gets whether the TextView includes extra top and bottom padding to make 11277 * room for accents that go above the normal ascent and descent. 11278 * 11279 * @see #setIncludeFontPadding(boolean) 11280 * 11281 * @attr ref android.R.styleable#TextView_includeFontPadding 11282 */ 11283 @InspectableProperty 11284 public boolean getIncludeFontPadding() { 11285 return mIncludePad; 11286 } 11287 11288 /** @hide */ 11289 @VisibleForTesting 11290 public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 11291 11292 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)11293 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 11294 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 11295 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 11296 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 11297 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 11298 11299 int width; 11300 int height; 11301 11302 BoringLayout.Metrics boring = UNKNOWN_BORING; 11303 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 11304 11305 if (mTextDir == null) { 11306 mTextDir = getTextDirectionHeuristic(); 11307 } 11308 11309 int des = -1; 11310 boolean fromexisting = false; 11311 final float widthLimit = (widthMode == MeasureSpec.AT_MOST) 11312 ? (float) widthSize : Float.MAX_VALUE; 11313 11314 if (widthMode == MeasureSpec.EXACTLY) { 11315 // Parent has told us how big to be. So be it. 11316 width = widthSize; 11317 } else { 11318 if (mLayout != null && mEllipsize == null) { 11319 des = desired(mLayout, mUseBoundsForWidth); 11320 } 11321 11322 if (des < 0) { 11323 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, 11324 isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(), 11325 mBoring); 11326 if (boring != null) { 11327 mBoring = boring; 11328 } 11329 } else { 11330 fromexisting = true; 11331 } 11332 11333 if (boring == null || boring == UNKNOWN_BORING) { 11334 if (des < 0) { 11335 des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0, 11336 mTransformed.length(), mTextPaint, mTextDir, widthLimit, 11337 mUseBoundsForWidth)); 11338 } 11339 width = des; 11340 } else { 11341 if (mUseBoundsForWidth) { 11342 RectF bbox = boring.getDrawingBoundingBox(); 11343 float rightMax = Math.max(bbox.right, boring.width); 11344 float leftMin = Math.min(bbox.left, 0); 11345 width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin)); 11346 } else { 11347 width = boring.width; 11348 } 11349 } 11350 11351 final Drawables dr = mDrawables; 11352 if (dr != null) { 11353 width = Math.max(width, dr.mDrawableWidthTop); 11354 width = Math.max(width, dr.mDrawableWidthBottom); 11355 } 11356 11357 if (mHint != null) { 11358 int hintDes = -1; 11359 int hintWidth; 11360 11361 if (mHintLayout != null && mEllipsize == null) { 11362 hintDes = desired(mHintLayout, mUseBoundsForWidth); 11363 } 11364 11365 if (hintDes < 0) { 11366 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 11367 isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(), 11368 mHintBoring); 11369 if (hintBoring != null) { 11370 mHintBoring = hintBoring; 11371 } 11372 } 11373 11374 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 11375 if (hintDes < 0) { 11376 hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0, 11377 mHint.length(), mTextPaint, mTextDir, widthLimit, 11378 mUseBoundsForWidth)); 11379 } 11380 hintWidth = hintDes; 11381 } else { 11382 hintWidth = hintBoring.width; 11383 } 11384 11385 if (hintWidth > width) { 11386 width = hintWidth; 11387 } 11388 } 11389 11390 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 11391 11392 if (mMaxWidthMode == EMS) { 11393 width = Math.min(width, mMaxWidth * getLineHeight()); 11394 } else { 11395 width = Math.min(width, mMaxWidth); 11396 } 11397 11398 if (mMinWidthMode == EMS) { 11399 width = Math.max(width, mMinWidth * getLineHeight()); 11400 } else { 11401 width = Math.max(width, mMinWidth); 11402 } 11403 11404 // Check against our minimum width 11405 width = Math.max(width, getSuggestedMinimumWidth()); 11406 11407 if (widthMode == MeasureSpec.AT_MOST) { 11408 width = Math.min(widthSize, width); 11409 } 11410 } 11411 11412 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 11413 int unpaddedWidth = want; 11414 11415 if (mHorizontallyScrolling) want = VERY_WIDE; 11416 11417 int hintWant = want; 11418 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 11419 11420 if (mLayout == null) { 11421 makeNewLayout(want, hintWant, boring, hintBoring, 11422 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 11423 } else { 11424 final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant) 11425 || (mLayout.getEllipsizedWidth() 11426 != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 11427 11428 final boolean widthChanged = (mHint == null) && (mEllipsize == null) 11429 && (want > mLayout.getWidth()) 11430 && (mLayout instanceof BoringLayout 11431 || (fromexisting && des >= 0 && des <= want)); 11432 11433 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 11434 11435 if (layoutChanged || maximumChanged) { 11436 if (!maximumChanged && widthChanged) { 11437 mLayout.increaseWidthTo(want); 11438 } else { 11439 makeNewLayout(want, hintWant, boring, hintBoring, 11440 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 11441 } 11442 } else { 11443 // Nothing has changed 11444 } 11445 } 11446 11447 if (heightMode == MeasureSpec.EXACTLY) { 11448 // Parent has told us how big to be. So be it. 11449 height = heightSize; 11450 mDesiredHeightAtMeasure = -1; 11451 } else { 11452 int desired = getDesiredHeight(); 11453 11454 height = desired; 11455 mDesiredHeightAtMeasure = desired; 11456 11457 if (heightMode == MeasureSpec.AT_MOST) { 11458 height = Math.min(desired, heightSize); 11459 } 11460 } 11461 11462 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 11463 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 11464 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 11465 } 11466 11467 /* 11468 * We didn't let makeNewLayout() register to bring the cursor into view, 11469 * so do it here if there is any possibility that it is needed. 11470 */ 11471 if (mMovement != null 11472 || mLayout.getWidth() > unpaddedWidth 11473 || mLayout.getHeight() > unpaddedHeight) { 11474 registerForPreDraw(); 11475 } else { 11476 scrollTo(0, 0); 11477 } 11478 11479 setMeasuredDimension(width, height); 11480 } 11481 11482 /** 11483 * Automatically computes and sets the text size. 11484 */ autoSizeText()11485 private void autoSizeText() { 11486 if (!isAutoSizeEnabled()) { 11487 return; 11488 } 11489 11490 if (mNeedsAutoSizeText) { 11491 if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) { 11492 return; 11493 } 11494 11495 final int availableWidth = mHorizontallyScrolling 11496 ? VERY_WIDE 11497 : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); 11498 final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom() 11499 - getExtendedPaddingTop(); 11500 11501 if (availableWidth <= 0 || availableHeight <= 0) { 11502 return; 11503 } 11504 11505 synchronized (TEMP_RECTF) { 11506 TEMP_RECTF.setEmpty(); 11507 TEMP_RECTF.right = availableWidth; 11508 TEMP_RECTF.bottom = availableHeight; 11509 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF); 11510 11511 if (optimalTextSize != getTextSize()) { 11512 setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize, 11513 false /* shouldRequestLayout */); 11514 11515 makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING, 11516 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 11517 false /* bringIntoView */); 11518 } 11519 } 11520 } 11521 // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing 11522 // after the next layout pass should set this to false. 11523 mNeedsAutoSizeText = true; 11524 } 11525 11526 /** 11527 * Performs a binary search to find the largest text size that will still fit within the size 11528 * available to this view. 11529 */ findLargestTextSizeWhichFits(RectF availableSpace)11530 private int findLargestTextSizeWhichFits(RectF availableSpace) { 11531 final int sizesCount = mAutoSizeTextSizesInPx.length; 11532 if (sizesCount == 0) { 11533 throw new IllegalStateException("No available text sizes to choose from."); 11534 } 11535 11536 int bestSizeIndex = 0; 11537 int lowIndex = bestSizeIndex + 1; 11538 int highIndex = sizesCount - 1; 11539 int sizeToTryIndex; 11540 while (lowIndex <= highIndex) { 11541 sizeToTryIndex = (lowIndex + highIndex) / 2; 11542 if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) { 11543 bestSizeIndex = lowIndex; 11544 lowIndex = sizeToTryIndex + 1; 11545 } else { 11546 highIndex = sizeToTryIndex - 1; 11547 bestSizeIndex = highIndex; 11548 } 11549 } 11550 11551 return mAutoSizeTextSizesInPx[bestSizeIndex]; 11552 } 11553 suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)11554 private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) { 11555 final CharSequence text = mTransformed != null 11556 ? mTransformed 11557 : getText(); 11558 final int maxLines = getMaxLines(); 11559 if (mTempTextPaint == null) { 11560 mTempTextPaint = new TextPaint(); 11561 } else { 11562 mTempTextPaint.reset(); 11563 } 11564 mTempTextPaint.set(getPaint()); 11565 mTempTextPaint.setTextSize(suggestedSizeInPx); 11566 11567 final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( 11568 text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); 11569 layoutBuilder.setAlignment(getLayoutAlignment()) 11570 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) 11571 .setIncludePad(getIncludeFontPadding()) 11572 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 11573 .setBreakStrategy(getBreakStrategy()) 11574 .setHyphenationFrequency(getHyphenationFrequency()) 11575 .setJustificationMode(getJustificationMode()) 11576 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 11577 .setTextDirection(getTextDirectionHeuristic()) 11578 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( 11579 mLineBreakStyle, mLineBreakWordStyle)) 11580 .setUseBoundsForWidth(mUseBoundsForWidth) 11581 .setMinimumFontMetrics(getResolvedMinimumFontMetrics()); 11582 11583 final StaticLayout layout = layoutBuilder.build(); 11584 11585 // Lines overflow. 11586 if (maxLines != -1 && layout.getLineCount() > maxLines) { 11587 return false; 11588 } 11589 11590 // Height overflow. 11591 if (layout.getHeight() > availableSpace.bottom) { 11592 return false; 11593 } 11594 11595 return true; 11596 } 11597 getDesiredHeight()11598 private int getDesiredHeight() { 11599 return Math.max( 11600 getDesiredHeight(mLayout, true), 11601 getDesiredHeight(mHintLayout, mEllipsize != null)); 11602 } 11603 getDesiredHeight(Layout layout, boolean cap)11604 private int getDesiredHeight(Layout layout, boolean cap) { 11605 if (layout == null) { 11606 return 0; 11607 } 11608 11609 /* 11610 * Don't cap the hint to a certain number of lines. 11611 * (Do cap it, though, if we have a maximum pixel height.) 11612 */ 11613 int desired = layout.getHeight(cap); 11614 11615 final Drawables dr = mDrawables; 11616 if (dr != null) { 11617 desired = Math.max(desired, dr.mDrawableHeightLeft); 11618 desired = Math.max(desired, dr.mDrawableHeightRight); 11619 } 11620 11621 int linecount = layout.getLineCount(); 11622 final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom(); 11623 desired += padding; 11624 11625 if (mMaxMode != LINES) { 11626 desired = Math.min(desired, mMaximum); 11627 } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout 11628 || layout instanceof BoringLayout)) { 11629 desired = layout.getLineTop(mMaximum); 11630 11631 if (dr != null) { 11632 desired = Math.max(desired, dr.mDrawableHeightLeft); 11633 desired = Math.max(desired, dr.mDrawableHeightRight); 11634 } 11635 11636 desired += padding; 11637 linecount = mMaximum; 11638 } 11639 11640 if (mMinMode == LINES) { 11641 if (linecount < mMinimum) { 11642 desired += getLineHeight() * (mMinimum - linecount); 11643 } 11644 } else { 11645 desired = Math.max(desired, mMinimum); 11646 } 11647 11648 // Check against our minimum height 11649 desired = Math.max(desired, getSuggestedMinimumHeight()); 11650 11651 return desired; 11652 } 11653 11654 /** 11655 * Check whether a change to the existing text layout requires a 11656 * new view layout. 11657 */ checkForResize()11658 private void checkForResize() { 11659 boolean sizeChanged = false; 11660 11661 if (mLayout != null) { 11662 // Check if our width changed 11663 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 11664 sizeChanged = true; 11665 invalidate(); 11666 } 11667 11668 // Check if our height changed 11669 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 11670 int desiredHeight = getDesiredHeight(); 11671 11672 if (desiredHeight != this.getHeight()) { 11673 sizeChanged = true; 11674 } 11675 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 11676 if (mDesiredHeightAtMeasure >= 0) { 11677 int desiredHeight = getDesiredHeight(); 11678 11679 if (desiredHeight != mDesiredHeightAtMeasure) { 11680 sizeChanged = true; 11681 } 11682 } 11683 } 11684 } 11685 11686 if (sizeChanged) { 11687 requestLayout(); 11688 // caller will have already invalidated 11689 } 11690 } 11691 11692 /** 11693 * Check whether entirely new text requires a new view layout 11694 * or merely a new text layout. 11695 */ 11696 @UnsupportedAppUsage checkForRelayout()11697 private void checkForRelayout() { 11698 // If we have a fixed width, we can just swap in a new text layout 11699 // if the text height stays the same or if the view height is fixed. 11700 11701 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT 11702 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) 11703 && (mHint == null || mHintLayout != null) 11704 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 11705 // Static width, so try making a new text layout. 11706 11707 int oldht = mLayout.getHeight(); 11708 int want = mLayout.getWidth(); 11709 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 11710 11711 /* 11712 * No need to bring the text into view, since the size is not 11713 * changing (unless we do the requestLayout(), in which case it 11714 * will happen at measure). 11715 */ 11716 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 11717 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 11718 false); 11719 11720 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 11721 // In a fixed-height view, so use our new text layout. 11722 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT 11723 && mLayoutParams.height != LayoutParams.MATCH_PARENT) { 11724 autoSizeText(); 11725 invalidate(); 11726 return; 11727 } 11728 11729 // Dynamic height, but height has stayed the same, 11730 // so use our new text layout. 11731 if (mLayout.getHeight() == oldht 11732 && (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 11733 autoSizeText(); 11734 invalidate(); 11735 return; 11736 } 11737 } 11738 11739 // We lose: the height has changed and we have a dynamic height. 11740 // Request a new view layout using our new text layout. 11741 requestLayout(); 11742 invalidate(); 11743 } else { 11744 // Dynamic width, so we have no choice but to request a new 11745 // view layout with a new text layout. 11746 nullLayouts(); 11747 requestLayout(); 11748 invalidate(); 11749 } 11750 } 11751 11752 @Override onLayout(boolean changed, int left, int top, int right, int bottom)11753 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 11754 super.onLayout(changed, left, top, right, bottom); 11755 if (mDeferScroll >= 0) { 11756 int curs = mDeferScroll; 11757 mDeferScroll = -1; 11758 bringPointIntoView(Math.min(curs, mText.length())); 11759 } 11760 // Call auto-size after the width and height have been calculated. 11761 autoSizeText(); 11762 } 11763 isShowingHint()11764 private boolean isShowingHint() { 11765 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint) && !mHideHint; 11766 } 11767 11768 /** 11769 * Returns true if anything changed. 11770 */ 11771 @UnsupportedAppUsage bringTextIntoView()11772 private boolean bringTextIntoView() { 11773 Layout layout = isShowingHint() ? mHintLayout : mLayout; 11774 int line = 0; 11775 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 11776 line = layout.getLineCount() - 1; 11777 } 11778 11779 Layout.Alignment a = layout.getParagraphAlignment(line); 11780 int dir = layout.getParagraphDirection(line); 11781 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 11782 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 11783 int ht = layout.getHeight(); 11784 11785 int scrollx, scrolly; 11786 11787 // Convert to left, center, or right alignment. 11788 if (a == Layout.Alignment.ALIGN_NORMAL) { 11789 a = dir == Layout.DIR_LEFT_TO_RIGHT 11790 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 11791 } else if (a == Layout.Alignment.ALIGN_OPPOSITE) { 11792 a = dir == Layout.DIR_LEFT_TO_RIGHT 11793 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 11794 } 11795 11796 if (a == Layout.Alignment.ALIGN_CENTER) { 11797 /* 11798 * Keep centered if possible, or, if it is too wide to fit, 11799 * keep leading edge in view. 11800 */ 11801 11802 int left = (int) Math.floor(layout.getLineLeft(line)); 11803 int right = (int) Math.ceil(layout.getLineRight(line)); 11804 11805 if (right - left < hspace) { 11806 scrollx = (right + left) / 2 - hspace / 2; 11807 } else { 11808 if (dir < 0) { 11809 scrollx = right - hspace; 11810 } else { 11811 scrollx = left; 11812 } 11813 } 11814 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 11815 int right = (int) Math.ceil(layout.getLineRight(line)); 11816 scrollx = right - hspace; 11817 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 11818 scrollx = (int) Math.floor(layout.getLineLeft(line)); 11819 } 11820 11821 if (ht < vspace) { 11822 scrolly = 0; 11823 } else { 11824 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 11825 scrolly = ht - vspace; 11826 } else { 11827 scrolly = 0; 11828 } 11829 } 11830 11831 if (scrollx != mScrollX || scrolly != mScrollY) { 11832 scrollTo(scrollx, scrolly); 11833 return true; 11834 } else { 11835 return false; 11836 } 11837 } 11838 11839 /** 11840 * Move the point, specified by the offset, into the view if it is needed. 11841 * This has to be called after layout. Returns true if anything changed. 11842 */ bringPointIntoView(int offset)11843 public boolean bringPointIntoView(int offset) { 11844 return bringPointIntoView(offset, false); 11845 } 11846 11847 /** 11848 * Move the insertion position of the given offset into visible area of the View. 11849 * 11850 * If the View is focused or {@code requestRectWithoutFocus} is set to true, this API may call 11851 * {@link View#requestRectangleOnScreen(Rect)} to bring the point to the visible area if 11852 * necessary. 11853 * 11854 * @param offset an offset of the character. 11855 * @param requestRectWithoutFocus True for calling {@link View#requestRectangleOnScreen(Rect)} 11856 * in the unfocused state. False for calling it only the View has 11857 * the focus. 11858 * @return true if anything changed, otherwise false. 11859 * 11860 * @see #bringPointIntoView(int) 11861 */ bringPointIntoView(@ntRangefrom = 0) int offset, boolean requestRectWithoutFocus)11862 public boolean bringPointIntoView(@IntRange(from = 0) int offset, 11863 boolean requestRectWithoutFocus) { 11864 if (isLayoutRequested()) { 11865 mDeferScroll = offset; 11866 return false; 11867 } 11868 final int offsetTransformed = 11869 originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR); 11870 boolean changed = false; 11871 11872 Layout layout = isShowingHint() ? mHintLayout : mLayout; 11873 11874 if (layout == null) return changed; 11875 11876 int line = layout.getLineForOffset(offsetTransformed); 11877 11878 int grav; 11879 11880 switch (layout.getParagraphAlignment(line)) { 11881 case ALIGN_LEFT: 11882 grav = 1; 11883 break; 11884 case ALIGN_RIGHT: 11885 grav = -1; 11886 break; 11887 case ALIGN_NORMAL: 11888 grav = layout.getParagraphDirection(line); 11889 break; 11890 case ALIGN_OPPOSITE: 11891 grav = -layout.getParagraphDirection(line); 11892 break; 11893 case ALIGN_CENTER: 11894 default: 11895 grav = 0; 11896 break; 11897 } 11898 11899 // We only want to clamp the cursor to fit within the layout width 11900 // in left-to-right modes, because in a right to left alignment, 11901 // we want to scroll to keep the line-right on the screen, as other 11902 // lines are likely to have text flush with the right margin, which 11903 // we want to keep visible. 11904 // A better long-term solution would probably be to measure both 11905 // the full line and a blank-trimmed version, and, for example, use 11906 // the latter measurement for centering and right alignment, but for 11907 // the time being we only implement the cursor clamping in left to 11908 // right where it is most likely to be annoying. 11909 final boolean clamped = grav > 0; 11910 // FIXME: Is it okay to truncate this, or should we round? 11911 final int x = (int) layout.getPrimaryHorizontal(offsetTransformed, clamped); 11912 final int top = layout.getLineTop(line); 11913 final int bottom = layout.getLineTop(line + 1); 11914 11915 int left = (int) Math.floor(layout.getLineLeft(line)); 11916 int right = (int) Math.ceil(layout.getLineRight(line)); 11917 int ht = layout.getHeight(); 11918 11919 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 11920 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 11921 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 11922 // If cursor has been clamped, make sure we don't scroll. 11923 right = Math.max(x, left + hspace); 11924 } 11925 11926 int hslack = (bottom - top) / 2; 11927 int vslack = hslack; 11928 11929 if (vslack > vspace / 4) { 11930 vslack = vspace / 4; 11931 } 11932 if (hslack > hspace / 4) { 11933 hslack = hspace / 4; 11934 } 11935 11936 int hs = mScrollX; 11937 int vs = mScrollY; 11938 11939 if (top - vs < vslack) { 11940 vs = top - vslack; 11941 } 11942 if (bottom - vs > vspace - vslack) { 11943 vs = bottom - (vspace - vslack); 11944 } 11945 if (ht - vs < vspace) { 11946 vs = ht - vspace; 11947 } 11948 if (0 - vs > 0) { 11949 vs = 0; 11950 } 11951 11952 if (grav != 0) { 11953 if (x - hs < hslack) { 11954 hs = x - hslack; 11955 } 11956 if (x - hs > hspace - hslack) { 11957 hs = x - (hspace - hslack); 11958 } 11959 } 11960 11961 if (grav < 0) { 11962 if (left - hs > 0) { 11963 hs = left; 11964 } 11965 if (right - hs < hspace) { 11966 hs = right - hspace; 11967 } 11968 } else if (grav > 0) { 11969 if (right - hs < hspace) { 11970 hs = right - hspace; 11971 } 11972 if (left - hs > 0) { 11973 hs = left; 11974 } 11975 } else /* grav == 0 */ { 11976 if (right - left <= hspace) { 11977 /* 11978 * If the entire text fits, center it exactly. 11979 */ 11980 hs = left - (hspace - (right - left)) / 2; 11981 } else if (x > right - hslack) { 11982 /* 11983 * If we are near the right edge, keep the right edge 11984 * at the edge of the view. 11985 */ 11986 hs = right - hspace; 11987 } else if (x < left + hslack) { 11988 /* 11989 * If we are near the left edge, keep the left edge 11990 * at the edge of the view. 11991 */ 11992 hs = left; 11993 } else if (left > hs) { 11994 /* 11995 * Is there whitespace visible at the left? Fix it if so. 11996 */ 11997 hs = left; 11998 } else if (right < hs + hspace) { 11999 /* 12000 * Is there whitespace visible at the right? Fix it if so. 12001 */ 12002 hs = right - hspace; 12003 } else { 12004 /* 12005 * Otherwise, float as needed. 12006 */ 12007 if (x - hs < hslack) { 12008 hs = x - hslack; 12009 } 12010 if (x - hs > hspace - hslack) { 12011 hs = x - (hspace - hslack); 12012 } 12013 } 12014 } 12015 12016 if (hs != mScrollX || vs != mScrollY) { 12017 if (mScroller == null) { 12018 scrollTo(hs, vs); 12019 } else { 12020 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 12021 int dx = hs - mScrollX; 12022 int dy = vs - mScrollY; 12023 12024 if (duration > ANIMATED_SCROLL_GAP) { 12025 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 12026 awakenScrollBars(mScroller.getDuration()); 12027 invalidate(); 12028 } else { 12029 if (!mScroller.isFinished()) { 12030 mScroller.abortAnimation(); 12031 } 12032 12033 scrollBy(dx, dy); 12034 } 12035 12036 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 12037 } 12038 12039 changed = true; 12040 } 12041 12042 if (requestRectWithoutFocus || isFocused()) { 12043 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 12044 // requestRectangleOnScreen() is in terms of content coordinates. 12045 12046 // The offsets here are to ensure the rectangle we are using is 12047 // within our view bounds, in case the cursor is on the far left 12048 // or right. If it isn't withing the bounds, then this request 12049 // will be ignored. 12050 if (mTempRect == null) mTempRect = new Rect(); 12051 mTempRect.set(x - 2, top, x + 2, bottom); 12052 getInterestingRect(mTempRect, line); 12053 mTempRect.offset(mScrollX, mScrollY); 12054 12055 if (requestRectangleOnScreen(mTempRect)) { 12056 changed = true; 12057 } 12058 } 12059 12060 return changed; 12061 } 12062 12063 /** 12064 * Move the cursor, if needed, so that it is at an offset that is visible 12065 * to the user. This will not move the cursor if it represents more than 12066 * one character (a selection range). This will only work if the 12067 * TextView contains spannable text; otherwise it will do nothing. 12068 * 12069 * @return True if the cursor was actually moved, false otherwise. 12070 */ moveCursorToVisibleOffset()12071 public boolean moveCursorToVisibleOffset() { 12072 if (!(mText instanceof Spannable)) { 12073 return false; 12074 } 12075 int start = getSelectionStartTransformed(); 12076 int end = getSelectionEndTransformed(); 12077 if (start != end) { 12078 return false; 12079 } 12080 12081 // First: make sure the line is visible on screen: 12082 12083 int line = mLayout.getLineForOffset(start); 12084 12085 final int top = mLayout.getLineTop(line); 12086 final int bottom = mLayout.getLineTop(line + 1); 12087 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 12088 int vslack = (bottom - top) / 2; 12089 if (vslack > vspace / 4) { 12090 vslack = vspace / 4; 12091 } 12092 final int vs = mScrollY; 12093 12094 if (top < (vs + vslack)) { 12095 line = mLayout.getLineForVertical(vs + vslack + (bottom - top)); 12096 } else if (bottom > (vspace + vs - vslack)) { 12097 line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top)); 12098 } 12099 12100 // Next: make sure the character is visible on screen: 12101 12102 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 12103 final int hs = mScrollX; 12104 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 12105 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs); 12106 12107 // line might contain bidirectional text 12108 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 12109 final int highChar = leftChar > rightChar ? leftChar : rightChar; 12110 12111 int newStart = start; 12112 if (newStart < lowChar) { 12113 newStart = lowChar; 12114 } else if (newStart > highChar) { 12115 newStart = highChar; 12116 } 12117 12118 if (newStart != start) { 12119 Selection.setSelection(mSpannable, 12120 transformedToOriginal(newStart, OffsetMapping.MAP_STRATEGY_CURSOR)); 12121 return true; 12122 } 12123 12124 return false; 12125 } 12126 12127 @Override computeScroll()12128 public void computeScroll() { 12129 if (mScroller != null) { 12130 if (mScroller.computeScrollOffset()) { 12131 mScrollX = mScroller.getCurrX(); 12132 mScrollY = mScroller.getCurrY(); 12133 invalidateParentCaches(); 12134 postInvalidate(); // So we draw again 12135 } 12136 } 12137 } 12138 getInterestingRect(Rect r, int line)12139 private void getInterestingRect(Rect r, int line) { 12140 convertFromViewportToContentCoordinates(r); 12141 12142 // Rectangle can can be expanded on first and last line to take 12143 // padding into account. 12144 // TODO Take left/right padding into account too? 12145 if (line == 0) r.top -= getExtendedPaddingTop(); 12146 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 12147 } 12148 convertFromViewportToContentCoordinates(Rect r)12149 private void convertFromViewportToContentCoordinates(Rect r) { 12150 final int horizontalOffset = viewportToContentHorizontalOffset(); 12151 r.left += horizontalOffset; 12152 r.right += horizontalOffset; 12153 12154 final int verticalOffset = viewportToContentVerticalOffset(); 12155 r.top += verticalOffset; 12156 r.bottom += verticalOffset; 12157 } 12158 convertFromScreenToContentCoordinates(PointF point)12159 private PointF convertFromScreenToContentCoordinates(PointF point) { 12160 if (Flags.handwritingGestureWithTransformation()) { 12161 if (mTempMatrix == null) { 12162 mTempMatrix = new Matrix(); 12163 } 12164 Matrix matrix = mTempMatrix; 12165 matrix.reset(); 12166 transformMatrixToLocal(matrix); 12167 matrix.postTranslate( 12168 -viewportToContentHorizontalOffset(), 12169 -viewportToContentVerticalOffset() 12170 ); 12171 12172 float[] copy = new float[] { point.x, point.y }; 12173 matrix.mapPoints(copy); 12174 return new PointF(copy[0], copy[1]); 12175 } 12176 int[] screenToViewport = getLocationOnScreen(); 12177 PointF copy = new PointF(point); 12178 copy.offset( 12179 -(screenToViewport[0] + viewportToContentHorizontalOffset()), 12180 -(screenToViewport[1] + viewportToContentVerticalOffset())); 12181 return copy; 12182 } 12183 convertFromScreenToContentCoordinates(RectF rect)12184 private RectF convertFromScreenToContentCoordinates(RectF rect) { 12185 if (Flags.handwritingGestureWithTransformation()) { 12186 if (mTempMatrix == null) { 12187 mTempMatrix = new Matrix(); 12188 } 12189 Matrix matrix = mTempMatrix; 12190 matrix.reset(); 12191 transformMatrixToLocal(matrix); 12192 matrix.postTranslate( 12193 -viewportToContentHorizontalOffset(), 12194 -viewportToContentVerticalOffset() 12195 ); 12196 12197 RectF copy = new RectF(rect); 12198 matrix.mapRect(copy); 12199 return copy; 12200 } 12201 int[] screenToViewport = getLocationOnScreen(); 12202 RectF copy = new RectF(rect); 12203 copy.offset( 12204 -(screenToViewport[0] + viewportToContentHorizontalOffset()), 12205 -(screenToViewport[1] + viewportToContentVerticalOffset())); 12206 return copy; 12207 } 12208 viewportToContentHorizontalOffset()12209 int viewportToContentHorizontalOffset() { 12210 return getCompoundPaddingLeft() - mScrollX; 12211 } 12212 12213 @UnsupportedAppUsage viewportToContentVerticalOffset()12214 int viewportToContentVerticalOffset() { 12215 int offset = getExtendedPaddingTop() - mScrollY; 12216 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 12217 offset += getVerticalOffset(false); 12218 } 12219 return offset; 12220 } 12221 12222 @Override debug(int depth)12223 public void debug(int depth) { 12224 super.debug(depth); 12225 12226 String output = debugIndent(depth); 12227 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 12228 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 12229 + "} "; 12230 12231 if (mText != null) { 12232 12233 output += "mText=\"" + mText + "\" "; 12234 if (mLayout != null) { 12235 output += "mLayout width=" + mLayout.getWidth() 12236 + " height=" + mLayout.getHeight(); 12237 } 12238 } else { 12239 output += "mText=NULL"; 12240 } 12241 Log.d(VIEW_LOG_TAG, output); 12242 } 12243 12244 /** 12245 * Convenience for {@link Selection#getSelectionStart}. 12246 */ 12247 @ViewDebug.ExportedProperty(category = "text") getSelectionStart()12248 public int getSelectionStart() { 12249 return Selection.getSelectionStart(getText()); 12250 } 12251 12252 /** 12253 * Convenience for {@link Selection#getSelectionEnd}. 12254 */ 12255 @ViewDebug.ExportedProperty(category = "text") getSelectionEnd()12256 public int getSelectionEnd() { 12257 return Selection.getSelectionEnd(getText()); 12258 } 12259 12260 /** 12261 * Calculates the rectangles which should be highlighted to indicate a selection between start 12262 * and end and feeds them into the given {@link Layout.SelectionRectangleConsumer}. 12263 * 12264 * @param start the starting index of the selection 12265 * @param end the ending index of the selection 12266 * @param consumer the {@link Layout.SelectionRectangleConsumer} which will receive the 12267 * generated rectangles. It will be called every time a rectangle is generated. 12268 * @hide 12269 */ getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer)12270 public void getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer) { 12271 final int transformedStart = 12272 originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR); 12273 final int transformedEnd = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR); 12274 mLayout.getSelection(transformedStart, transformedEnd, consumer); 12275 } 12276 getSelectionStartTransformed()12277 int getSelectionStartTransformed() { 12278 final int start = getSelectionStart(); 12279 if (start < 0) return start; 12280 return originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR); 12281 } 12282 getSelectionEndTransformed()12283 int getSelectionEndTransformed() { 12284 final int end = getSelectionEnd(); 12285 if (end < 0) return end; 12286 return originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR); 12287 } 12288 12289 /** 12290 * Return true iff there is a selection of nonzero length inside this text view. 12291 */ hasSelection()12292 public boolean hasSelection() { 12293 final int selectionStart = getSelectionStart(); 12294 final int selectionEnd = getSelectionEnd(); 12295 final int selectionMin; 12296 final int selectionMax; 12297 if (selectionStart < selectionEnd) { 12298 selectionMin = selectionStart; 12299 selectionMax = selectionEnd; 12300 } else { 12301 selectionMin = selectionEnd; 12302 selectionMax = selectionStart; 12303 } 12304 12305 return selectionMin >= 0 && selectionMax > 0 && selectionMin != selectionMax; 12306 } 12307 12308 /** 12309 * @hide 12310 */ 12311 @VisibleForTesting getSelectedText()12312 public String getSelectedText() { 12313 if (!hasSelection()) { 12314 return null; 12315 } 12316 12317 final int start = getSelectionStart(); 12318 final int end = getSelectionEnd(); 12319 return String.valueOf( 12320 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end)); 12321 } 12322 12323 /** 12324 * Sets the properties of this field (lines, horizontally scrolling, 12325 * transformation method) to be for a single-line input. 12326 * 12327 * @attr ref android.R.styleable#TextView_singleLine 12328 */ setSingleLine()12329 public void setSingleLine() { 12330 setSingleLine(true); 12331 } 12332 12333 /** 12334 * Sets the properties of this field to transform input to ALL CAPS 12335 * display. This may use a "small caps" formatting if available. 12336 * This setting will be ignored if this field is editable or selectable. 12337 * 12338 * This call replaces the current transformation method. Disabling this 12339 * will not necessarily restore the previous behavior from before this 12340 * was enabled. 12341 * 12342 * @see #setTransformationMethod(TransformationMethod) 12343 * @attr ref android.R.styleable#TextView_textAllCaps 12344 */ 12345 @android.view.RemotableViewMethod setAllCaps(boolean allCaps)12346 public void setAllCaps(boolean allCaps) { 12347 if (allCaps) { 12348 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 12349 } else { 12350 setTransformationMethod(null); 12351 } 12352 } 12353 12354 /** 12355 * 12356 * Checks whether the transformation method applied to this TextView is set to ALL CAPS. 12357 * @return Whether the current transformation method is for ALL CAPS. 12358 * 12359 * @see #setAllCaps(boolean) 12360 * @see #setTransformationMethod(TransformationMethod) 12361 */ 12362 @InspectableProperty(name = "textAllCaps") isAllCaps()12363 public boolean isAllCaps() { 12364 final TransformationMethod method = getTransformationMethod(); 12365 return method != null && method instanceof AllCapsTransformationMethod; 12366 } 12367 12368 /** 12369 * If true, sets the properties of this field (number of lines, horizontally scrolling, 12370 * transformation method) to be for a single-line input; if false, restores these to the default 12371 * conditions. 12372 * 12373 * Note that the default conditions are not necessarily those that were in effect prior this 12374 * method, and you may want to reset these properties to your custom values. 12375 * 12376 * Note that due to performance reasons, by setting single line for the EditText, the maximum 12377 * text length is set to 5000 if no other character limitation are applied. 12378 * 12379 * @attr ref android.R.styleable#TextView_singleLine 12380 */ 12381 @android.view.RemotableViewMethod setSingleLine(boolean singleLine)12382 public void setSingleLine(boolean singleLine) { 12383 // Could be used, but may break backward compatibility. 12384 // if (mSingleLine == singleLine) return; 12385 setInputTypeSingleLine(singleLine); 12386 applySingleLine(singleLine, true, true, true); 12387 } 12388 12389 /** 12390 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 12391 * @param singleLine 12392 */ setInputTypeSingleLine(boolean singleLine)12393 private void setInputTypeSingleLine(boolean singleLine) { 12394 if (mEditor != null 12395 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 12396 == EditorInfo.TYPE_CLASS_TEXT) { 12397 if (singleLine) { 12398 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 12399 } else { 12400 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 12401 } 12402 } 12403 } 12404 applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines, boolean changeMaxLength)12405 private void applySingleLine(boolean singleLine, boolean applyTransformation, 12406 boolean changeMaxLines, boolean changeMaxLength) { 12407 mSingleLine = singleLine; 12408 12409 if (singleLine) { 12410 setLines(1); 12411 setHorizontallyScrolling(true); 12412 if (applyTransformation) { 12413 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 12414 } 12415 12416 if (!changeMaxLength) return; 12417 12418 // Single line length filter is only applicable editable text. 12419 if (mBufferType != BufferType.EDITABLE) return; 12420 12421 final InputFilter[] prevFilters = getFilters(); 12422 for (InputFilter filter: getFilters()) { 12423 // We don't add LengthFilter if already there. 12424 if (filter instanceof InputFilter.LengthFilter) return; 12425 } 12426 12427 if (mSingleLineLengthFilter == null) { 12428 mSingleLineLengthFilter = new InputFilter.LengthFilter( 12429 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT); 12430 } 12431 12432 final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1]; 12433 System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length); 12434 newFilters[prevFilters.length] = mSingleLineLengthFilter; 12435 12436 setFilters(newFilters); 12437 12438 // Since filter doesn't apply to existing text, trigger filter by setting text. 12439 setText(getText()); 12440 } else { 12441 if (changeMaxLines) { 12442 setMaxLines(Integer.MAX_VALUE); 12443 } 12444 setHorizontallyScrolling(false); 12445 if (applyTransformation) { 12446 setTransformationMethod(null); 12447 } 12448 12449 if (!changeMaxLength) return; 12450 12451 // Single line length filter is only applicable editable text. 12452 if (mBufferType != BufferType.EDITABLE) return; 12453 12454 final InputFilter[] prevFilters = getFilters(); 12455 if (prevFilters.length == 0) return; 12456 12457 // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated 12458 // single line char limit filter. 12459 if (mSingleLineLengthFilter == null) return; 12460 12461 // If we need to remove mSingleLineLengthFilter, we need to allocate another array. 12462 // Since filter list is expected to be small and want to avoid unnecessary array 12463 // allocation, check if there is mSingleLengthFilter first. 12464 int targetIndex = -1; 12465 for (int i = 0; i < prevFilters.length; ++i) { 12466 if (prevFilters[i] == mSingleLineLengthFilter) { 12467 targetIndex = i; 12468 break; 12469 } 12470 } 12471 if (targetIndex == -1) return; // not found. Do nothing. 12472 12473 if (prevFilters.length == 1) { 12474 setFilters(NO_FILTERS); 12475 return; 12476 } 12477 12478 // Create new array which doesn't include mSingleLengthFilter. 12479 final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1]; 12480 System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex); 12481 System.arraycopy( 12482 prevFilters, 12483 targetIndex + 1, 12484 newFilters, 12485 targetIndex, 12486 prevFilters.length - targetIndex - 1); 12487 setFilters(newFilters); 12488 mSingleLineLengthFilter = null; 12489 } 12490 } 12491 12492 /** 12493 * Causes words in the text that are longer than the view's width 12494 * to be ellipsized instead of broken in the middle. You may also 12495 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 12496 * to constrain the text to a single line. Use <code>null</code> 12497 * to turn off ellipsizing. 12498 * 12499 * If {@link #setMaxLines} has been used to set two or more lines, 12500 * only {@link android.text.TextUtils.TruncateAt#END} and 12501 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 12502 * (other ellipsizing types will not do anything). 12503 * 12504 * @attr ref android.R.styleable#TextView_ellipsize 12505 */ setEllipsize(TextUtils.TruncateAt where)12506 public void setEllipsize(TextUtils.TruncateAt where) { 12507 // TruncateAt is an enum. != comparison is ok between these singleton objects. 12508 if (mEllipsize != where) { 12509 mEllipsize = where; 12510 12511 if (mLayout != null) { 12512 nullLayouts(); 12513 requestLayout(); 12514 invalidate(); 12515 } 12516 } 12517 } 12518 12519 /** 12520 * Sets how many times to repeat the marquee animation. Only applied if the 12521 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 12522 * 12523 * @see #getMarqueeRepeatLimit() 12524 * 12525 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 12526 */ setMarqueeRepeatLimit(int marqueeLimit)12527 public void setMarqueeRepeatLimit(int marqueeLimit) { 12528 mMarqueeRepeatLimit = marqueeLimit; 12529 } 12530 12531 /** 12532 * Gets the number of times the marquee animation is repeated. Only meaningful if the 12533 * TextView has marquee enabled. 12534 * 12535 * @return the number of times the marquee animation is repeated. -1 if the animation 12536 * repeats indefinitely 12537 * 12538 * @see #setMarqueeRepeatLimit(int) 12539 * 12540 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 12541 */ 12542 @InspectableProperty getMarqueeRepeatLimit()12543 public int getMarqueeRepeatLimit() { 12544 return mMarqueeRepeatLimit; 12545 } 12546 12547 /** 12548 * Returns where, if anywhere, words that are longer than the view 12549 * is wide should be ellipsized. 12550 */ 12551 @InspectableProperty 12552 @ViewDebug.ExportedProperty getEllipsize()12553 public TextUtils.TruncateAt getEllipsize() { 12554 return mEllipsize; 12555 } 12556 12557 /** 12558 * Set the TextView so that when it takes focus, all the text is 12559 * selected. 12560 * 12561 * @attr ref android.R.styleable#TextView_selectAllOnFocus 12562 */ 12563 @android.view.RemotableViewMethod setSelectAllOnFocus(boolean selectAllOnFocus)12564 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 12565 createEditorIfNeeded(); 12566 mEditor.mSelectAllOnFocus = selectAllOnFocus; 12567 12568 if (selectAllOnFocus && !(mText instanceof Spannable)) { 12569 setText(mText, BufferType.SPANNABLE); 12570 } 12571 } 12572 12573 /** 12574 * Set whether the cursor is visible. The default is true. Note that this property only 12575 * makes sense for editable TextView. If IME is consuming the input, the cursor will always be 12576 * invisible, visibility will be updated as the last state when IME does not consume 12577 * the input anymore. 12578 * 12579 * @see #isCursorVisible() 12580 * 12581 * @attr ref android.R.styleable#TextView_cursorVisible 12582 */ 12583 @android.view.RemotableViewMethod setCursorVisible(boolean visible)12584 public void setCursorVisible(boolean visible) { 12585 mCursorVisibleFromAttr = visible; 12586 updateCursorVisibleInternal(); 12587 } 12588 12589 /** 12590 * Sets the IME is consuming the input and make the cursor invisible if {@code imeConsumesInput} 12591 * is {@code true}. Otherwise, make the cursor visible. 12592 * 12593 * @param imeConsumesInput {@code true} if IME is consuming the input 12594 * 12595 * @hide 12596 */ setImeConsumesInput(boolean imeConsumesInput)12597 public void setImeConsumesInput(boolean imeConsumesInput) { 12598 mImeIsConsumingInput = imeConsumesInput; 12599 updateCursorVisibleInternal(); 12600 } 12601 updateCursorVisibleInternal()12602 private void updateCursorVisibleInternal() { 12603 boolean visible = mCursorVisibleFromAttr && !mImeIsConsumingInput; 12604 if (visible && mEditor == null) return; // visible is the default value with no edit data 12605 createEditorIfNeeded(); 12606 if (mEditor.mCursorVisible != visible) { 12607 mEditor.mCursorVisible = visible; 12608 invalidate(); 12609 12610 mEditor.makeBlink(); 12611 12612 // InsertionPointCursorController depends on mCursorVisible 12613 mEditor.prepareCursorControllers(); 12614 } 12615 } 12616 12617 /** 12618 * @return whether or not the cursor is visible (assuming this TextView is editable). This 12619 * method may return {@code false} when the IME is consuming the input even if the 12620 * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)} 12621 * is called. 12622 * 12623 * @see #setCursorVisible(boolean) 12624 * 12625 * @attr ref android.R.styleable#TextView_cursorVisible 12626 */ 12627 @InspectableProperty isCursorVisible()12628 public boolean isCursorVisible() { 12629 // true is the default value 12630 return mEditor == null ? true : mEditor.mCursorVisible; 12631 } 12632 12633 /** 12634 * @return whether cursor is visible without regard to {@code mImeIsConsumingInput}. 12635 * {@code true} is the default value. 12636 * 12637 * @see #setCursorVisible(boolean) 12638 * @hide 12639 */ isCursorVisibleFromAttr()12640 public boolean isCursorVisibleFromAttr() { 12641 return mCursorVisibleFromAttr; 12642 } 12643 canMarquee()12644 private boolean canMarquee() { 12645 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 12646 return width > 0 && (mLayout.getLineWidth(0) > width 12647 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null 12648 && mSavedMarqueeModeLayout.getLineWidth(0) > width)); 12649 } 12650 12651 /** 12652 * @hide 12653 */ 12654 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startMarquee()12655 protected void startMarquee() { 12656 // Do not ellipsize EditText 12657 if (getKeyListener() != null) return; 12658 12659 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 12660 return; 12661 } 12662 12663 if ((mMarquee == null || mMarquee.isStopped()) && isAggregatedVisible() 12664 && (isFocused() || isSelected()) && getLineCount() == 1 && canMarquee()) { 12665 12666 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 12667 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 12668 final Layout tmp = mLayout; 12669 mLayout = mSavedMarqueeModeLayout; 12670 mSavedMarqueeModeLayout = tmp; 12671 setHorizontalFadingEdgeEnabled(true); 12672 requestLayout(); 12673 invalidate(); 12674 } 12675 12676 if (mMarquee == null) mMarquee = new Marquee(this); 12677 mMarquee.start(mMarqueeRepeatLimit); 12678 } 12679 } 12680 12681 /** 12682 * @hide 12683 */ stopMarquee()12684 protected void stopMarquee() { 12685 if (mMarquee != null && !mMarquee.isStopped()) { 12686 mMarquee.stop(); 12687 } 12688 12689 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 12690 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 12691 final Layout tmp = mSavedMarqueeModeLayout; 12692 mSavedMarqueeModeLayout = mLayout; 12693 mLayout = tmp; 12694 setHorizontalFadingEdgeEnabled(false); 12695 requestLayout(); 12696 invalidate(); 12697 } 12698 } 12699 12700 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startStopMarquee(boolean start)12701 private void startStopMarquee(boolean start) { 12702 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 12703 if (start) { 12704 startMarquee(); 12705 } else { 12706 stopMarquee(); 12707 } 12708 } 12709 } 12710 12711 /** 12712 * This method is called when the text is changed, in case any subclasses 12713 * would like to know. 12714 * 12715 * Within <code>text</code>, the <code>lengthAfter</code> characters 12716 * beginning at <code>start</code> have just replaced old text that had 12717 * length <code>lengthBefore</code>. It is an error to attempt to make 12718 * changes to <code>text</code> from this callback. 12719 * 12720 * @param text The text the TextView is displaying 12721 * @param start The offset of the start of the range of the text that was 12722 * modified 12723 * @param lengthBefore The length of the former text that has been replaced 12724 * @param lengthAfter The length of the replacement modified text 12725 */ onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)12726 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 12727 // intentionally empty, template pattern method can be overridden by subclasses 12728 } 12729 12730 /** 12731 * This method is called when the selection has changed, in case any 12732 * subclasses would like to know. 12733 * </p> 12734 * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs 12735 * the accessibility subsystem about the selection change. 12736 * </p> 12737 * 12738 * @param selStart The new selection start location. 12739 * @param selEnd The new selection end location. 12740 */ 12741 @CallSuper onSelectionChanged(int selStart, int selEnd)12742 protected void onSelectionChanged(int selStart, int selEnd) { 12743 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 12744 } 12745 12746 /** 12747 * Adds a TextWatcher to the list of those whose methods are called 12748 * whenever this TextView's text changes. 12749 * <p> 12750 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 12751 * not called after {@link #setText} calls. Now, doing {@link #setText} 12752 * if there are any text changed listeners forces the buffer type to 12753 * Editable if it would not otherwise be and does call this method. 12754 */ addTextChangedListener(TextWatcher watcher)12755 public void addTextChangedListener(TextWatcher watcher) { 12756 if (mListeners == null) { 12757 mListeners = new ArrayList<TextWatcher>(); 12758 } 12759 12760 mListeners.add(watcher); 12761 } 12762 12763 /** 12764 * Removes the specified TextWatcher from the list of those whose 12765 * methods are called 12766 * whenever this TextView's text changes. 12767 */ removeTextChangedListener(TextWatcher watcher)12768 public void removeTextChangedListener(TextWatcher watcher) { 12769 if (mListeners != null) { 12770 int i = mListeners.indexOf(watcher); 12771 12772 if (i >= 0) { 12773 mListeners.remove(i); 12774 } 12775 } 12776 } 12777 sendBeforeTextChanged(CharSequence text, int start, int before, int after)12778 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 12779 if (mListeners != null) { 12780 final ArrayList<TextWatcher> list = mListeners; 12781 final int count = list.size(); 12782 for (int i = 0; i < count; i++) { 12783 list.get(i).beforeTextChanged(text, start, before, after); 12784 } 12785 } 12786 12787 // The spans that are inside or intersect the modified region no longer make sense 12788 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 12789 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 12790 } 12791 12792 // Removes all spans that are inside or actually overlap the start..end range removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)12793 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 12794 if (!(mText instanceof Editable)) return; 12795 Editable text = (Editable) mText; 12796 12797 T[] spans = text.getSpans(start, end, type); 12798 ArrayList<T> spansToRemove = new ArrayList<>(); 12799 for (T span : spans) { 12800 final int spanStart = text.getSpanStart(span); 12801 final int spanEnd = text.getSpanEnd(span); 12802 if (spanEnd == start || spanStart == end) continue; 12803 spansToRemove.add(span); 12804 } 12805 for (T span : spansToRemove) { 12806 text.removeSpan(span); 12807 } 12808 } 12809 removeAdjacentSuggestionSpans(final int pos)12810 void removeAdjacentSuggestionSpans(final int pos) { 12811 if (!(mText instanceof Editable)) return; 12812 final Editable text = (Editable) mText; 12813 12814 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 12815 final int length = spans.length; 12816 for (int i = 0; i < length; i++) { 12817 final int spanStart = text.getSpanStart(spans[i]); 12818 final int spanEnd = text.getSpanEnd(spans[i]); 12819 if (spanEnd == pos || spanStart == pos) { 12820 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 12821 text.removeSpan(spans[i]); 12822 } 12823 } 12824 } 12825 } 12826 12827 /** 12828 * Not private so it can be called from an inner class without going 12829 * through a thunk. 12830 */ sendOnTextChanged(CharSequence text, int start, int before, int after)12831 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 12832 if (mListeners != null) { 12833 final ArrayList<TextWatcher> list = mListeners; 12834 final int count = list.size(); 12835 for (int i = 0; i < count; i++) { 12836 list.get(i).onTextChanged(text, start, before, after); 12837 } 12838 } 12839 12840 if (mEditor != null) mEditor.sendOnTextChanged(start, before, after); 12841 } 12842 12843 /** 12844 * Not private so it can be called from an inner class without going 12845 * through a thunk. 12846 */ sendAfterTextChanged(Editable text)12847 void sendAfterTextChanged(Editable text) { 12848 if (mListeners != null) { 12849 final ArrayList<TextWatcher> list = mListeners; 12850 final int count = list.size(); 12851 for (int i = 0; i < count; i++) { 12852 list.get(i).afterTextChanged(text); 12853 } 12854 } 12855 12856 notifyListeningManagersAfterTextChanged(); 12857 12858 hideErrorIfUnchanged(); 12859 } 12860 12861 /** 12862 * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are 12863 * interested on text changes. 12864 */ notifyListeningManagersAfterTextChanged()12865 private void notifyListeningManagersAfterTextChanged() { 12866 12867 // Autofill 12868 if (isAutofillable()) { 12869 // It is important to not check whether the view is important for autofill 12870 // since the user can trigger autofill manually on not important views. 12871 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 12872 if (afm != null) { 12873 if (android.view.autofill.Helper.sVerbose) { 12874 Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged"); 12875 } 12876 afm.notifyValueChanged(TextView.this); 12877 } 12878 } 12879 12880 notifyContentCaptureTextChanged(); 12881 } 12882 12883 /** 12884 * Notifies the ContentCapture service that the text of the view has changed (only if 12885 * ContentCapture has been notified of this view's existence already). 12886 * 12887 * @hide 12888 */ notifyContentCaptureTextChanged()12889 public void notifyContentCaptureTextChanged() { 12890 // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead 12891 // of using isLaidout(), so it's not called in cases where it's laid out but a 12892 // notifyAppeared was not sent. 12893 if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) { 12894 final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class); 12895 if (cm != null && cm.isContentCaptureEnabled()) { 12896 final ContentCaptureSession session = getContentCaptureSession(); 12897 if (session != null) { 12898 // TODO(b/111276913): pass flags when edited by user / add CTS test 12899 session.notifyViewTextChanged(getAutofillId(), getText()); 12900 } 12901 } 12902 } 12903 } 12904 isAutofillable()12905 private boolean isAutofillable() { 12906 // It is important to not check whether the view is important for autofill 12907 // since the user can trigger autofill manually on not important views. 12908 return getAutofillType() != AUTOFILL_TYPE_NONE; 12909 } 12910 updateAfterEdit()12911 void updateAfterEdit() { 12912 invalidate(); 12913 int curs = getSelectionStart(); 12914 12915 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 12916 registerForPreDraw(); 12917 } 12918 12919 checkForResize(); 12920 12921 if (curs >= 0) { 12922 mHighlightPathBogus = true; 12923 if (mEditor != null) mEditor.makeBlink(); 12924 bringPointIntoView(curs); 12925 } 12926 } 12927 12928 /** 12929 * Not private so it can be called from an inner class without going 12930 * through a thunk. 12931 */ handleTextChanged(CharSequence buffer, int start, int before, int after)12932 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 12933 sLastCutCopyOrTextChangedTime = 0; 12934 12935 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 12936 if (ims == null || ims.mBatchEditNesting == 0) { 12937 updateAfterEdit(); 12938 } 12939 if (ims != null) { 12940 ims.mContentChanged = true; 12941 if (ims.mChangedStart < 0) { 12942 ims.mChangedStart = start; 12943 ims.mChangedEnd = start + before; 12944 } else { 12945 ims.mChangedStart = Math.min(ims.mChangedStart, start); 12946 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 12947 } 12948 ims.mChangedDelta += after - before; 12949 } 12950 resetErrorChangedFlag(); 12951 sendOnTextChanged(buffer, start, before, after); 12952 onTextChanged(buffer, start, before, after); 12953 12954 mHideHint = false; 12955 clearGesturePreviewHighlight(); 12956 } 12957 12958 /** 12959 * Not private so it can be called from an inner class without going 12960 * through a thunk. 12961 */ spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)12962 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 12963 // XXX Make the start and end move together if this ends up 12964 // spending too much time invalidating. 12965 12966 boolean selChanged = false; 12967 int newSelStart = -1, newSelEnd = -1; 12968 12969 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 12970 12971 if (what == Selection.SELECTION_END) { 12972 selChanged = true; 12973 newSelEnd = newStart; 12974 12975 if (oldStart >= 0 || newStart >= 0) { 12976 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 12977 checkForResize(); 12978 registerForPreDraw(); 12979 if (mEditor != null) mEditor.makeBlink(); 12980 } 12981 } 12982 12983 if (what == Selection.SELECTION_START) { 12984 selChanged = true; 12985 newSelStart = newStart; 12986 12987 if (oldStart >= 0 || newStart >= 0) { 12988 int end = Selection.getSelectionEnd(buf); 12989 invalidateCursor(end, oldStart, newStart); 12990 } 12991 } 12992 12993 if (selChanged) { 12994 clearGesturePreviewHighlight(); 12995 mHighlightPathBogus = true; 12996 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 12997 12998 if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) { 12999 if (newSelStart < 0) { 13000 newSelStart = Selection.getSelectionStart(buf); 13001 } 13002 if (newSelEnd < 0) { 13003 newSelEnd = Selection.getSelectionEnd(buf); 13004 } 13005 13006 if (mEditor != null) { 13007 mEditor.refreshTextActionMode(); 13008 if (!hasSelection() 13009 && mEditor.getTextActionMode() == null && hasTransientState()) { 13010 // User generated selection has been removed. 13011 setHasTransientState(false); 13012 } 13013 } 13014 onSelectionChanged(newSelStart, newSelEnd); 13015 } 13016 } 13017 13018 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle 13019 || what instanceof CharacterStyle) { 13020 if (ims == null || ims.mBatchEditNesting == 0) { 13021 invalidate(); 13022 mHighlightPathBogus = true; 13023 checkForResize(); 13024 } else { 13025 ims.mContentChanged = true; 13026 } 13027 if (mEditor != null) { 13028 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 13029 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 13030 mEditor.invalidateHandlesAndActionMode(); 13031 } 13032 } 13033 13034 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 13035 mHighlightPathBogus = true; 13036 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 13037 ims.mSelectionModeChanged = true; 13038 } 13039 13040 if (Selection.getSelectionStart(buf) >= 0) { 13041 if (ims == null || ims.mBatchEditNesting == 0) { 13042 invalidateCursor(); 13043 } else { 13044 ims.mCursorChanged = true; 13045 } 13046 } 13047 } 13048 13049 if (what instanceof ParcelableSpan) { 13050 // If this is a span that can be sent to a remote process, 13051 // the current extract editor would be interested in it. 13052 if (ims != null && ims.mExtractedTextRequest != null) { 13053 if (ims.mBatchEditNesting != 0) { 13054 if (oldStart >= 0) { 13055 if (ims.mChangedStart > oldStart) { 13056 ims.mChangedStart = oldStart; 13057 } 13058 if (ims.mChangedStart > oldEnd) { 13059 ims.mChangedStart = oldEnd; 13060 } 13061 } 13062 if (newStart >= 0) { 13063 if (ims.mChangedStart > newStart) { 13064 ims.mChangedStart = newStart; 13065 } 13066 if (ims.mChangedStart > newEnd) { 13067 ims.mChangedStart = newEnd; 13068 } 13069 } 13070 } else { 13071 if (DEBUG_EXTRACT) { 13072 Log.v(LOG_TAG, "Span change outside of batch: " 13073 + oldStart + "-" + oldEnd + "," 13074 + newStart + "-" + newEnd + " " + what); 13075 } 13076 ims.mContentChanged = true; 13077 } 13078 } 13079 } 13080 13081 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 13082 && what instanceof SpellCheckSpan) { 13083 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 13084 } 13085 } 13086 13087 @Override onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)13088 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 13089 if (isTemporarilyDetached()) { 13090 // If we are temporarily in the detach state, then do nothing. 13091 super.onFocusChanged(focused, direction, previouslyFocusedRect); 13092 return; 13093 } 13094 13095 mHideHint = false; 13096 13097 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 13098 13099 if (focused) { 13100 if (mSpannable != null) { 13101 MetaKeyKeyListener.resetMetaState(mSpannable); 13102 } 13103 } 13104 13105 startStopMarquee(focused); 13106 13107 if (mTransformation != null) { 13108 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 13109 } 13110 13111 super.onFocusChanged(focused, direction, previouslyFocusedRect); 13112 } 13113 13114 @Override onWindowFocusChanged(boolean hasWindowFocus)13115 public void onWindowFocusChanged(boolean hasWindowFocus) { 13116 super.onWindowFocusChanged(hasWindowFocus); 13117 13118 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 13119 13120 startStopMarquee(hasWindowFocus); 13121 } 13122 13123 @Override onVisibilityChanged(View changedView, int visibility)13124 protected void onVisibilityChanged(View changedView, int visibility) { 13125 super.onVisibilityChanged(changedView, visibility); 13126 if (mEditor != null && visibility != VISIBLE) { 13127 mEditor.hideCursorAndSpanControllers(); 13128 stopTextActionMode(); 13129 } 13130 } 13131 13132 @Override onVisibilityAggregated(boolean isVisible)13133 public void onVisibilityAggregated(boolean isVisible) { 13134 super.onVisibilityAggregated(isVisible); 13135 startStopMarquee(isVisible); 13136 } 13137 13138 /** 13139 * Use {@link BaseInputConnection#removeComposingSpans 13140 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 13141 * state from this text view. 13142 */ clearComposingText()13143 public void clearComposingText() { 13144 if (mText instanceof Spannable) { 13145 BaseInputConnection.removeComposingSpans(mSpannable); 13146 } 13147 } 13148 13149 @Override setSelected(boolean selected)13150 public void setSelected(boolean selected) { 13151 boolean wasSelected = isSelected(); 13152 13153 super.setSelected(selected); 13154 13155 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 13156 if (selected) { 13157 startMarquee(); 13158 } else { 13159 stopMarquee(); 13160 } 13161 } 13162 } 13163 13164 /** 13165 * Called from onTouchEvent() to prevent the touches by secondary fingers. 13166 * Dragging on handles can revise cursor/selection, so can dragging on the text view. 13167 * This method is a lock to avoid processing multiple fingers on both text view and handles. 13168 * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work. 13169 * 13170 * @param event The motion event that is being handled and carries the pointer info. 13171 * @param fromHandleView true if the event is delivered to selection handle or insertion 13172 * handle; false if this event is delivered to TextView. 13173 * @return Returns true to indicate that onTouchEvent() can continue processing the motion 13174 * event, otherwise false. 13175 * - Always returns true for the first finger. 13176 * - For secondary fingers, if the first or current finger is from TextView, returns false. 13177 * This is to make touch mutually exclusive between the TextView and the handles, but 13178 * not among the handles. 13179 */ isFromPrimePointer(MotionEvent event, boolean fromHandleView)13180 boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) { 13181 boolean res = true; 13182 if (mPrimePointerId == NO_POINTER_ID) { 13183 mPrimePointerId = event.getPointerId(0); 13184 mIsPrimePointerFromHandleView = fromHandleView; 13185 } else if (mPrimePointerId != event.getPointerId(0)) { 13186 res = mIsPrimePointerFromHandleView && fromHandleView; 13187 } 13188 if (event.getActionMasked() == MotionEvent.ACTION_UP 13189 || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { 13190 mPrimePointerId = -1; 13191 } 13192 return res; 13193 } 13194 13195 @Override onTouchEvent(MotionEvent event)13196 public boolean onTouchEvent(MotionEvent event) { 13197 if (DEBUG_CURSOR) { 13198 logCursor("onTouchEvent", "%d: %s (%f,%f)", 13199 event.getSequenceNumber(), 13200 MotionEvent.actionToString(event.getActionMasked()), 13201 event.getX(), event.getY()); 13202 } 13203 mLastInputSource = event.getSource(); 13204 final int action = event.getActionMasked(); 13205 if (mEditor != null) { 13206 if (!isFromPrimePointer(event, false)) { 13207 return true; 13208 } 13209 13210 mEditor.onTouchEvent(event); 13211 13212 if (mEditor.mInsertionPointCursorController != null 13213 && mEditor.mInsertionPointCursorController.isCursorBeingModified()) { 13214 return true; 13215 } 13216 if (mEditor.mSelectionModifierCursorController != null 13217 && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { 13218 return true; 13219 } 13220 } 13221 13222 final boolean superResult = super.onTouchEvent(event); 13223 if (DEBUG_CURSOR) { 13224 logCursor("onTouchEvent", "superResult=%s", superResult); 13225 } 13226 13227 /* 13228 * Don't handle the release after a long press, because it will move the selection away from 13229 * whatever the menu action was trying to affect. If the long press should have triggered an 13230 * insertion action mode, we can now actually show it. 13231 */ 13232 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 13233 mEditor.mDiscardNextActionUp = false; 13234 if (DEBUG_CURSOR) { 13235 logCursor("onTouchEvent", "release after long press detected"); 13236 } 13237 if (mEditor.mIsInsertionActionModeStartPending) { 13238 mEditor.startInsertionActionMode(); 13239 mEditor.mIsInsertionActionModeStartPending = false; 13240 } 13241 return superResult; 13242 } 13243 13244 // At this point, the event is not a long press, otherwise it would be handled above. 13245 if (Flags.handwritingEndOfLineTap() && action == MotionEvent.ACTION_UP 13246 && shouldStartHandwritingForEndOfLineTap(event)) { 13247 InputMethodManager imm = getInputMethodManager(); 13248 if (imm != null) { 13249 imm.startStylusHandwriting(this); 13250 return true; 13251 } 13252 } 13253 13254 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) 13255 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 13256 13257 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 13258 && mText instanceof Spannable && mLayout != null) { 13259 boolean handled = false; 13260 13261 if (mMovement != null) { 13262 handled |= mMovement.onTouchEvent(this, mSpannable, event); 13263 } 13264 13265 final boolean textIsSelectable = isTextSelectable(); 13266 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 13267 // The LinkMovementMethod which should handle taps on links has not been installed 13268 // on non editable text that support text selection. 13269 // We reproduce its behavior here to open links for these. 13270 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(), 13271 getSelectionEnd(), ClickableSpan.class); 13272 13273 if (links.length > 0) { 13274 links[0].onClick(this); 13275 handled = true; 13276 } 13277 } 13278 13279 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 13280 // Show the IME, except when selecting in read-only text. 13281 final InputMethodManager imm = getInputMethodManager(); 13282 viewClicked(imm); 13283 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null 13284 && !showAutofillDialog()) { 13285 imm.showSoftInput(this, 0); 13286 } 13287 13288 // The above condition ensures that the mEditor is not null 13289 mEditor.onTouchUpEvent(event); 13290 13291 handled = true; 13292 } 13293 13294 if (handled) { 13295 return true; 13296 } 13297 } 13298 13299 return superResult; 13300 } 13301 13302 /** 13303 * If handwriting is supported, the TextView is already focused and not empty, and the cursor is 13304 * at the end of a line, a stylus tap after the end of the line will trigger handwriting. 13305 */ shouldStartHandwritingForEndOfLineTap(MotionEvent actionUpEvent)13306 private boolean shouldStartHandwritingForEndOfLineTap(MotionEvent actionUpEvent) { 13307 if (!onCheckIsTextEditor() 13308 || !isEnabled() 13309 || !isAutoHandwritingEnabled() 13310 || TextUtils.isEmpty(mText) 13311 || didTouchFocusSelect() 13312 || mLayout == null 13313 || !actionUpEvent.isStylusPointer()) { 13314 return false; 13315 } 13316 int cursorOffset = getSelectionStart(); 13317 if (cursorOffset < 0 || getSelectionEnd() != cursorOffset) { 13318 return false; 13319 } 13320 int cursorLine = mLayout.getLineForOffset(cursorOffset); 13321 int cursorLineEnd = mLayout.getLineEnd(cursorLine); 13322 if (cursorLine != mLayout.getLineCount() - 1) { 13323 cursorLineEnd--; 13324 } 13325 if (cursorLineEnd != cursorOffset) { 13326 return false; 13327 } 13328 // Check that the stylus down point is within the same line as the cursor. 13329 if (getLineAtCoordinate(actionUpEvent.getY()) != cursorLine) { 13330 return false; 13331 } 13332 // Check that the stylus down point is after the end of the line. 13333 float localX = convertToLocalHorizontalCoordinate(actionUpEvent.getX()); 13334 if (mLayout.getParagraphDirection(cursorLine) == Layout.DIR_RIGHT_TO_LEFT 13335 ? localX >= mLayout.getLineLeft(cursorLine) 13336 : localX <= mLayout.getLineRight(cursorLine)) { 13337 return false; 13338 } 13339 return isStylusHandwritingAvailable(); 13340 } 13341 13342 /** 13343 * Returns true when need to show UIs, e.g. floating toolbar, etc, for finger based interaction. 13344 * 13345 * @return true if UIs need to show for finger interaciton. false if UIs are not necessary. 13346 * @hide 13347 */ showUIForTouchScreen()13348 public final boolean showUIForTouchScreen() { 13349 return (mLastInputSource & InputDevice.SOURCE_TOUCHSCREEN) 13350 == InputDevice.SOURCE_TOUCHSCREEN; 13351 } 13352 13353 /** 13354 * The fill dialog UI is a more conspicuous and efficient interface than dropdown UI. 13355 * If autofill suggestions are available when the user clicks on a field that supports filling 13356 * the dialog UI, Autofill will pop up a fill dialog. The dialog will take up a larger area 13357 * to display the datasets, so it is easy for users to pay attention to the datasets and 13358 * selecting a dataset. The autofill dialog is shown as the bottom sheet, the better 13359 * experience is not to show the IME if there is a fill dialog. 13360 */ showAutofillDialog()13361 private boolean showAutofillDialog() { 13362 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 13363 if (afm != null) { 13364 return afm.showAutofillDialog(this); 13365 } 13366 return false; 13367 } 13368 13369 @Override onGenericMotionEvent(MotionEvent event)13370 public boolean onGenericMotionEvent(MotionEvent event) { 13371 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 13372 try { 13373 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) { 13374 return true; 13375 } 13376 } catch (AbstractMethodError ex) { 13377 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 13378 // Ignore its absence in case third party applications implemented the 13379 // interface directly. 13380 } 13381 } 13382 return super.onGenericMotionEvent(event); 13383 } 13384 13385 @Override onCreateContextMenu(ContextMenu menu)13386 protected void onCreateContextMenu(ContextMenu menu) { 13387 if (mEditor != null) { 13388 mEditor.onCreateContextMenu(menu); 13389 } 13390 } 13391 13392 @Override showContextMenu()13393 public boolean showContextMenu() { 13394 if (mEditor != null) { 13395 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); 13396 } 13397 return super.showContextMenu(); 13398 } 13399 13400 @Override showContextMenu(float x, float y)13401 public boolean showContextMenu(float x, float y) { 13402 if (mEditor != null) { 13403 mEditor.setContextMenuAnchor(x, y); 13404 } 13405 return super.showContextMenu(x, y); 13406 } 13407 13408 /** 13409 * @return True iff this TextView contains a text that can be edited, or if this is 13410 * a selectable TextView. 13411 */ 13412 @UnsupportedAppUsage isTextEditable()13413 boolean isTextEditable() { 13414 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 13415 } 13416 13417 /** 13418 * @return true if this TextView could be filled by an Autofill service. Note that disabled 13419 * fields can still be filled. 13420 */ 13421 @UnsupportedAppUsage isTextAutofillable()13422 boolean isTextAutofillable() { 13423 return mText instanceof Editable && onCheckIsTextEditor(); 13424 } 13425 13426 /** 13427 * Returns true, only while processing a touch gesture, if the initial 13428 * touch down event caused focus to move to the text view and as a result 13429 * its selection changed. Only valid while processing the touch gesture 13430 * of interest, in an editable text view. 13431 */ didTouchFocusSelect()13432 public boolean didTouchFocusSelect() { 13433 return mEditor != null && mEditor.mTouchFocusSelected; 13434 } 13435 13436 @Override cancelLongPress()13437 public void cancelLongPress() { 13438 super.cancelLongPress(); 13439 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 13440 } 13441 13442 @Override onTrackballEvent(MotionEvent event)13443 public boolean onTrackballEvent(MotionEvent event) { 13444 if (mMovement != null && mSpannable != null && mLayout != null) { 13445 if (mMovement.onTrackballEvent(this, mSpannable, event)) { 13446 return true; 13447 } 13448 } 13449 13450 return super.onTrackballEvent(event); 13451 } 13452 13453 /** 13454 * Sets the Scroller used for producing a scrolling animation 13455 * 13456 * @param s A Scroller instance 13457 */ setScroller(Scroller s)13458 public void setScroller(Scroller s) { 13459 mScroller = s; 13460 } 13461 13462 @Override getLeftFadingEdgeStrength()13463 protected float getLeftFadingEdgeStrength() { 13464 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 13465 final Marquee marquee = mMarquee; 13466 if (marquee.shouldDrawLeftFade()) { 13467 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f); 13468 } else { 13469 return 0.0f; 13470 } 13471 } else if (getLineCount() == 1) { 13472 final float lineLeft = getLayout().getLineLeft(0); 13473 if (lineLeft > mScrollX) return 0.0f; 13474 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft); 13475 } 13476 return super.getLeftFadingEdgeStrength(); 13477 } 13478 13479 @Override getRightFadingEdgeStrength()13480 protected float getRightFadingEdgeStrength() { 13481 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 13482 final Marquee marquee = mMarquee; 13483 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll()); 13484 } else if (getLineCount() == 1) { 13485 final float rightEdge = mScrollX + 13486 (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight()); 13487 final float lineRight = getLayout().getLineRight(0); 13488 if (lineRight < rightEdge) return 0.0f; 13489 return getHorizontalFadingEdgeStrength(rightEdge, lineRight); 13490 } 13491 return super.getRightFadingEdgeStrength(); 13492 } 13493 13494 /** 13495 * Calculates the fading edge strength as the ratio of the distance between two 13496 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute 13497 * value for the distance calculation. 13498 * 13499 * @param position1 A horizontal position. 13500 * @param position2 A horizontal position. 13501 * @return Fading edge strength between [0.0f, 1.0f]. 13502 */ 13503 @FloatRange(from = 0.0, to = 1.0) getHorizontalFadingEdgeStrength(float position1, float position2)13504 private float getHorizontalFadingEdgeStrength(float position1, float position2) { 13505 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength(); 13506 if (horizontalFadingEdgeLength == 0) return 0.0f; 13507 final float diff = Math.abs(position1 - position2); 13508 if (diff > horizontalFadingEdgeLength) return 1.0f; 13509 return diff / horizontalFadingEdgeLength; 13510 } 13511 isMarqueeFadeEnabled()13512 private boolean isMarqueeFadeEnabled() { 13513 return mEllipsize == TextUtils.TruncateAt.MARQUEE 13514 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 13515 } 13516 13517 @Override computeHorizontalScrollRange()13518 protected int computeHorizontalScrollRange() { 13519 if (mLayout != null) { 13520 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT 13521 ? (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 13522 } 13523 13524 return super.computeHorizontalScrollRange(); 13525 } 13526 13527 @Override computeVerticalScrollRange()13528 protected int computeVerticalScrollRange() { 13529 if (mLayout != null) { 13530 return mLayout.getHeight(); 13531 } 13532 return super.computeVerticalScrollRange(); 13533 } 13534 13535 @Override computeVerticalScrollExtent()13536 protected int computeVerticalScrollExtent() { 13537 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 13538 } 13539 13540 @Override findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)13541 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 13542 super.findViewsWithText(outViews, searched, flags); 13543 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 13544 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 13545 String searchedLowerCase = searched.toString().toLowerCase(); 13546 String textLowerCase = mText.toString().toLowerCase(); 13547 if (textLowerCase.contains(searchedLowerCase)) { 13548 outViews.add(this); 13549 } 13550 } 13551 } 13552 13553 /** 13554 * Type of the text buffer that defines the characteristics of the text such as static, 13555 * styleable, or editable. 13556 */ 13557 public enum BufferType { 13558 NORMAL, SPANNABLE, EDITABLE 13559 } 13560 13561 /** 13562 * Returns the TextView_textColor attribute from the TypedArray, if set, or 13563 * the TextAppearance_textColor from the TextView_textAppearance attribute, 13564 * if TextView_textColor was not set directly. 13565 * 13566 * @removed 13567 */ getTextColors(Context context, TypedArray attrs)13568 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 13569 if (attrs == null) { 13570 // Preserve behavior prior to removal of this API. 13571 throw new NullPointerException(); 13572 } 13573 13574 // It's not safe to use this method from apps. The parameter 'attrs' 13575 // must have been obtained using the TextView filter array which is not 13576 // available to the SDK. As such, we grab a default TypedArray with the 13577 // right filter instead here. 13578 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 13579 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 13580 if (colors == null) { 13581 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 13582 if (ap != 0) { 13583 final TypedArray appearance = context.obtainStyledAttributes( 13584 ap, R.styleable.TextAppearance); 13585 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 13586 appearance.recycle(); 13587 } 13588 } 13589 a.recycle(); 13590 13591 return colors; 13592 } 13593 13594 /** 13595 * Returns the default color from the TextView_textColor attribute from the 13596 * AttributeSet, if set, or the default color from the 13597 * TextAppearance_textColor from the TextView_textAppearance attribute, if 13598 * TextView_textColor was not set directly. 13599 * 13600 * @removed 13601 */ getTextColor(Context context, TypedArray attrs, int def)13602 public static int getTextColor(Context context, TypedArray attrs, int def) { 13603 final ColorStateList colors = getTextColors(context, attrs); 13604 if (colors == null) { 13605 return def; 13606 } else { 13607 return colors.getDefaultColor(); 13608 } 13609 } 13610 13611 @Override onKeyShortcut(int keyCode, KeyEvent event)13612 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 13613 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 13614 // Handle Ctrl-only shortcuts. 13615 switch (keyCode) { 13616 case KeyEvent.KEYCODE_A: 13617 if (canSelectText()) { 13618 return onTextContextMenuItem(ID_SELECT_ALL); 13619 } 13620 break; 13621 case KeyEvent.KEYCODE_Z: 13622 if (canUndo()) { 13623 return onTextContextMenuItem(ID_UNDO); 13624 } 13625 break; 13626 case KeyEvent.KEYCODE_X: 13627 if (canCut()) { 13628 return onTextContextMenuItem(ID_CUT); 13629 } 13630 break; 13631 case KeyEvent.KEYCODE_C: 13632 if (canCopy()) { 13633 return onTextContextMenuItem(ID_COPY); 13634 } 13635 break; 13636 case KeyEvent.KEYCODE_V: 13637 if (canPaste()) { 13638 return onTextContextMenuItem(ID_PASTE); 13639 } 13640 break; 13641 case KeyEvent.KEYCODE_Y: 13642 if (canRedo()) { 13643 return onTextContextMenuItem(ID_REDO); 13644 } 13645 break; 13646 } 13647 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { 13648 // Handle Ctrl-Shift shortcuts. 13649 switch (keyCode) { 13650 case KeyEvent.KEYCODE_Z: 13651 if (canRedo()) { 13652 return onTextContextMenuItem(ID_REDO); 13653 } 13654 break; 13655 case KeyEvent.KEYCODE_V: 13656 if (canPaste()) { 13657 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); 13658 } 13659 } 13660 } 13661 return super.onKeyShortcut(keyCode, event); 13662 } 13663 13664 /** 13665 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 13666 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 13667 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 13668 * sufficient. 13669 */ canSelectText()13670 boolean canSelectText() { 13671 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 13672 } 13673 13674 /** 13675 * Test based on the <i>intrinsic</i> charateristics of the TextView. 13676 * The text must be spannable and the movement method must allow for arbitary selection. 13677 * 13678 * See also {@link #canSelectText()}. 13679 */ textCanBeSelected()13680 boolean textCanBeSelected() { 13681 // prepareCursorController() relies on this method. 13682 // If you change this condition, make sure prepareCursorController is called anywhere 13683 // the value of this condition might be changed. 13684 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 13685 return isTextEditable() 13686 || (isTextSelectable() && mText instanceof Spannable && isEnabled()); 13687 } 13688 13689 @UnsupportedAppUsage getTextServicesLocale(boolean allowNullLocale)13690 private Locale getTextServicesLocale(boolean allowNullLocale) { 13691 // Start fetching the text services locale asynchronously. 13692 updateTextServicesLocaleAsync(); 13693 // If !allowNullLocale and there is no cached text services locale, just return the default 13694 // locale. 13695 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 13696 : mCurrentSpellCheckerLocaleCache; 13697 } 13698 13699 /** 13700 * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in 13701 * this {@link TextView}. 13702 * 13703 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 13704 * other apps may need to set this so that the system can user right user's resources and 13705 * services such as input methods and spell checkers.</p> 13706 * 13707 * @param user {@link UserHandle} who is considered to be the owner of the text shown in this 13708 * {@link TextView}. {@code null} to reset {@link #mTextOperationUser}. 13709 * @hide 13710 */ 13711 @RequiresPermission(INTERACT_ACROSS_USERS_FULL) setTextOperationUser(@ullable UserHandle user)13712 public final void setTextOperationUser(@Nullable UserHandle user) { 13713 if (Objects.equals(mTextOperationUser, user)) { 13714 return; 13715 } 13716 if (user != null && !Process.myUserHandle().equals(user)) { 13717 // Just for preventing people from accidentally using this hidden API without 13718 // the required permission. The same permission is also checked in the system server. 13719 if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL) 13720 != PackageManager.PERMISSION_GRANTED) { 13721 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required." 13722 + " userId=" + user.getIdentifier() 13723 + " callingUserId" + UserHandle.myUserId()); 13724 } 13725 } 13726 mTextOperationUser = user; 13727 // Invalidate some resources 13728 mCurrentSpellCheckerLocaleCache = null; 13729 if (mEditor != null) { 13730 mEditor.onTextOperationUserChanged(); 13731 } 13732 } 13733 13734 @Override isAutoHandwritingEnabled()13735 public boolean isAutoHandwritingEnabled() { 13736 return super.isAutoHandwritingEnabled() && !isAnyPasswordInputType(); 13737 } 13738 13739 /** @hide */ 13740 @Override shouldTrackHandwritingArea()13741 public boolean shouldTrackHandwritingArea() { 13742 // The handwriting initiator tracks all editable TextViews regardless of whether handwriting 13743 // is supported, so that it can show an error message for unsupported editable TextViews. 13744 return super.shouldTrackHandwritingArea() 13745 || (Flags.handwritingUnsupportedMessage() && onCheckIsTextEditor()); 13746 } 13747 13748 /** @hide */ 13749 @Override isStylusHandwritingAvailable()13750 public boolean isStylusHandwritingAvailable() { 13751 if (mTextOperationUser == null) { 13752 return super.isStylusHandwritingAvailable(); 13753 } 13754 final InputMethodManager imm = getInputMethodManager(); 13755 return imm.isStylusHandwritingAvailableAsUser(mTextOperationUser); 13756 } 13757 13758 @Nullable getTextServicesManagerForUser()13759 final TextServicesManager getTextServicesManagerForUser() { 13760 return getServiceManagerForUser("android", TextServicesManager.class); 13761 } 13762 13763 @Nullable getClipboardManagerForUser()13764 final ClipboardManager getClipboardManagerForUser() { 13765 return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class); 13766 } 13767 13768 @Nullable getTextClassificationManagerForUser()13769 final TextClassificationManager getTextClassificationManagerForUser() { 13770 return getServiceManagerForUser( 13771 getContext().getPackageName(), TextClassificationManager.class); 13772 } 13773 13774 @Nullable getServiceManagerForUser(String packageName, Class<T> managerClazz)13775 final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) { 13776 if (mTextOperationUser == null) { 13777 return getContext().getSystemService(managerClazz); 13778 } 13779 try { 13780 Context context = getContext().createPackageContextAsUser( 13781 packageName, 0 /* flags */, mTextOperationUser); 13782 return context.getSystemService(managerClazz); 13783 } catch (PackageManager.NameNotFoundException e) { 13784 return null; 13785 } 13786 } 13787 13788 /** 13789 * Starts {@link Activity} as a text-operation user if it is specified with 13790 * {@link #setTextOperationUser(UserHandle)}. 13791 * 13792 * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p> 13793 * 13794 * @param intent The description of the activity to start. 13795 */ startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)13796 void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) { 13797 if (mTextOperationUser != null) { 13798 getContext().startActivityAsUser(intent, mTextOperationUser); 13799 } else { 13800 getContext().startActivity(intent); 13801 } 13802 } 13803 13804 /** 13805 * This is a temporary method. Future versions may support multi-locale text. 13806 * Caveat: This method may not return the latest text services locale, but this should be 13807 * acceptable and it's more important to make this method asynchronous. 13808 * 13809 * @return The locale that should be used for a word iterator 13810 * in this TextView, based on the current spell checker settings, 13811 * the current IME's locale, or the system default locale. 13812 * Please note that a word iterator in this TextView is different from another word iterator 13813 * used by SpellChecker.java of TextView. This method should be used for the former. 13814 * @hide 13815 */ 13816 // TODO: Support multi-locale 13817 // TODO: Update the text services locale immediately after the keyboard locale is switched 13818 // by catching intent of keyboard switch event getTextServicesLocale()13819 public Locale getTextServicesLocale() { 13820 return getTextServicesLocale(false /* allowNullLocale */); 13821 } 13822 13823 /** 13824 * @return {@code true} if this TextView is specialized for showing and interacting with the 13825 * extracted text in a full-screen input method. 13826 * @hide 13827 */ isInExtractedMode()13828 public boolean isInExtractedMode() { 13829 return false; 13830 } 13831 13832 /** 13833 * @return {@code true} if this widget supports auto-sizing text and has been configured to 13834 * auto-size. 13835 */ isAutoSizeEnabled()13836 private boolean isAutoSizeEnabled() { 13837 return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE; 13838 } 13839 13840 /** 13841 * @return {@code true} if this TextView supports auto-sizing text to fit within its container. 13842 * @hide 13843 */ supportsAutoSizeText()13844 protected boolean supportsAutoSizeText() { 13845 return true; 13846 } 13847 13848 /** 13849 * This is a temporary method. Future versions may support multi-locale text. 13850 * Caveat: This method may not return the latest spell checker locale, but this should be 13851 * acceptable and it's more important to make this method asynchronous. 13852 * 13853 * @return The locale that should be used for a spell checker in this TextView, 13854 * based on the current spell checker settings, the current IME's locale, or the system default 13855 * locale. 13856 * @hide 13857 */ getSpellCheckerLocale()13858 public Locale getSpellCheckerLocale() { 13859 return getTextServicesLocale(true /* allowNullLocale */); 13860 } 13861 updateTextServicesLocaleAsync()13862 private void updateTextServicesLocaleAsync() { 13863 // AsyncTask.execute() uses a serial executor which means we don't have 13864 // to lock around updateTextServicesLocaleLocked() to prevent it from 13865 // being executed n times in parallel. 13866 AsyncTask.execute(new Runnable() { 13867 @Override 13868 public void run() { 13869 updateTextServicesLocaleLocked(); 13870 } 13871 }); 13872 } 13873 13874 @UnsupportedAppUsage updateTextServicesLocaleLocked()13875 private void updateTextServicesLocaleLocked() { 13876 final TextServicesManager textServicesManager = getTextServicesManagerForUser(); 13877 if (textServicesManager == null) { 13878 return; 13879 } 13880 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 13881 final Locale locale; 13882 if (subtype != null) { 13883 locale = subtype.getLocaleObject(); 13884 } else { 13885 locale = null; 13886 } 13887 mCurrentSpellCheckerLocaleCache = locale; 13888 } 13889 onLocaleChanged()13890 void onLocaleChanged() { 13891 mEditor.onLocaleChanged(); 13892 } 13893 13894 /** 13895 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 13896 * Made available to achieve a consistent behavior. 13897 * @hide 13898 */ getWordIterator()13899 public WordIterator getWordIterator() { 13900 if (mEditor != null) { 13901 return mEditor.getWordIterator(); 13902 } else { 13903 return null; 13904 } 13905 } 13906 13907 /** @hide */ 13908 @Override onPopulateAccessibilityEventInternal(AccessibilityEvent event)13909 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 13910 super.onPopulateAccessibilityEventInternal(event); 13911 13912 if (this.isAccessibilityDataSensitive() && !event.isAccessibilityDataSensitive()) { 13913 // This view's accessibility data is sensitive, but another view that generated this 13914 // event is not, so don't append this view's text to the event in order to prevent 13915 // sharing this view's contents with non-accessibility-tool services. 13916 return; 13917 } 13918 13919 final CharSequence text = getTextForAccessibility(); 13920 if (!TextUtils.isEmpty(text)) { 13921 event.getText().add(text); 13922 } 13923 } 13924 13925 @Override getAccessibilityClassName()13926 public CharSequence getAccessibilityClassName() { 13927 return TextView.class.getName(); 13928 } 13929 13930 /** @hide */ 13931 @Override onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)13932 protected void onProvideStructure(@NonNull ViewStructure structure, 13933 @ViewStructureType int viewFor, int flags) { 13934 super.onProvideStructure(structure, viewFor, flags); 13935 13936 final boolean isPassword = hasPasswordTransformationMethod() 13937 || isPasswordInputType(getInputType()); 13938 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 13939 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 13940 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 13941 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId); 13942 } 13943 if (mTextId != Resources.ID_NULL) { 13944 try { 13945 structure.setTextIdEntry(getResources().getResourceEntryName(mTextId)); 13946 } catch (Resources.NotFoundException e) { 13947 if (android.view.autofill.Helper.sVerbose) { 13948 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id " 13949 + mTextId + ": " + e.getMessage()); 13950 } 13951 } 13952 } 13953 String[] mimeTypes = getReceiveContentMimeTypes(); 13954 if (mimeTypes == null && mEditor != null) { 13955 // If the app hasn't set a listener for receiving content on this view (ie, 13956 // getReceiveContentMimeTypes() returns null), check if it implements the 13957 // keyboard image API and, if possible, use those MIME types as fallback. 13958 // This fallback is only in place for autofill, not other mechanisms for 13959 // inserting content. See AUTOFILL_NON_TEXT_REQUIRES_ON_RECEIVE_CONTENT_LISTENER 13960 // in TextViewOnReceiveContentListener for more info. 13961 mimeTypes = mEditor.getDefaultOnReceiveContentListener() 13962 .getFallbackMimeTypesForAutofill(this); 13963 } 13964 structure.setReceiveContentMimeTypes(mimeTypes); 13965 } 13966 13967 if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 13968 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 13969 if (mLayout == null) { 13970 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 13971 Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()"); 13972 } 13973 assumeLayout(); 13974 } 13975 Layout layout = mLayout; 13976 final int lineCount = layout.getLineCount(); 13977 if (lineCount <= 1) { 13978 // Simple case: this is a single line. 13979 final CharSequence text = getText(); 13980 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 13981 structure.setText(text); 13982 } else { 13983 structure.setText(text, getSelectionStart(), getSelectionEnd()); 13984 } 13985 } else { 13986 // Complex case: multi-line, could be scrolled or within a scroll container 13987 // so some lines are not visible. 13988 final int[] tmpCords = new int[2]; 13989 getLocationInWindow(tmpCords); 13990 final int topWindowLocation = tmpCords[1]; 13991 View root = this; 13992 ViewParent viewParent = getParent(); 13993 while (viewParent instanceof View) { 13994 root = (View) viewParent; 13995 viewParent = root.getParent(); 13996 } 13997 final int windowHeight = root.getHeight(); 13998 final int topLine; 13999 final int bottomLine; 14000 if (topWindowLocation >= 0) { 14001 // The top of the view is fully within its window; start text at line 0. 14002 topLine = getLineAtCoordinateUnclamped(0); 14003 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1); 14004 } else { 14005 // The top of hte window has scrolled off the top of the window; figure out 14006 // the starting line for this. 14007 topLine = getLineAtCoordinateUnclamped(-topWindowLocation); 14008 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation); 14009 } 14010 // We want to return some contextual lines above/below the lines that are 14011 // actually visible. 14012 int expandedTopLine = topLine - (bottomLine - topLine) / 2; 14013 if (expandedTopLine < 0) { 14014 expandedTopLine = 0; 14015 } 14016 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2; 14017 if (expandedBottomLine >= lineCount) { 14018 expandedBottomLine = lineCount - 1; 14019 } 14020 14021 // Convert lines into character offsets. 14022 int expandedTopChar = transformedToOriginal( 14023 layout.getLineStart(expandedTopLine), 14024 OffsetMapping.MAP_STRATEGY_CHARACTER); 14025 int expandedBottomChar = transformedToOriginal( 14026 layout.getLineEnd(expandedBottomLine), 14027 OffsetMapping.MAP_STRATEGY_CHARACTER); 14028 14029 // Take into account selection -- if there is a selection, we need to expand 14030 // the text we are returning to include that selection. 14031 final int selStart = getSelectionStart(); 14032 final int selEnd = getSelectionEnd(); 14033 if (selStart < selEnd) { 14034 if (selStart < expandedTopChar) { 14035 expandedTopChar = selStart; 14036 } 14037 if (selEnd > expandedBottomChar) { 14038 expandedBottomChar = selEnd; 14039 } 14040 } 14041 14042 // Get the text and trim it to the range we are reporting. 14043 CharSequence text = getText(); 14044 14045 if (text != null) { 14046 if (expandedTopChar > 0 || expandedBottomChar < text.length()) { 14047 // Cap the offsets to avoid an OOB exception. That can happen if the 14048 // displayed/layout text, on which these offsets are calculated, is longer 14049 // than the original text (such as when the view is translated by the 14050 // platform intelligence). 14051 // TODO(b/196433694): Figure out how to better handle the offset 14052 // calculations for this case (so we don't unnecessarily cutoff the original 14053 // text, for example). 14054 expandedTopChar = Math.min(expandedTopChar, text.length()); 14055 expandedBottomChar = Math.min(expandedBottomChar, text.length()); 14056 text = text.subSequence(expandedTopChar, expandedBottomChar); 14057 } 14058 14059 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 14060 structure.setText(text); 14061 } else { 14062 structure.setText(text, 14063 selStart - expandedTopChar, 14064 selEnd - expandedTopChar); 14065 14066 final int[] lineOffsets = new int[bottomLine - topLine + 1]; 14067 final int[] lineBaselines = new int[bottomLine - topLine + 1]; 14068 final int baselineOffset = getBaselineOffset(); 14069 for (int i = topLine; i <= bottomLine; i++) { 14070 lineOffsets[i - topLine] = transformedToOriginal(layout.getLineStart(i), 14071 OffsetMapping.MAP_STRATEGY_CHARACTER); 14072 lineBaselines[i - topLine] = 14073 layout.getLineBaseline(i) + baselineOffset; 14074 } 14075 structure.setTextLines(lineOffsets, lineBaselines); 14076 } 14077 } 14078 } 14079 14080 if (viewFor == VIEW_STRUCTURE_FOR_ASSIST 14081 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 14082 // Extract style information that applies to the TextView as a whole. 14083 int style = 0; 14084 int typefaceStyle = getTypefaceStyle(); 14085 if ((typefaceStyle & Typeface.BOLD) != 0) { 14086 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 14087 } 14088 if ((typefaceStyle & Typeface.ITALIC) != 0) { 14089 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC; 14090 } 14091 14092 // Global styles can also be set via TextView.setPaintFlags(). 14093 int paintFlags = mTextPaint.getFlags(); 14094 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) { 14095 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 14096 } 14097 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) { 14098 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE; 14099 } 14100 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { 14101 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU; 14102 } 14103 14104 // TextView does not have its own text background color. A background is either part 14105 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. 14106 structure.setTextStyle(getTextSize(), getCurrentTextColor(), 14107 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); 14108 } 14109 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 14110 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 14111 structure.setMinTextEms(getMinEms()); 14112 structure.setMaxTextEms(getMaxEms()); 14113 int maxLength = -1; 14114 for (InputFilter filter: getFilters()) { 14115 if (filter instanceof InputFilter.LengthFilter) { 14116 maxLength = ((InputFilter.LengthFilter) filter).getMax(); 14117 break; 14118 } 14119 } 14120 structure.setMaxTextLength(maxLength); 14121 } 14122 } 14123 if (mHintId != Resources.ID_NULL) { 14124 try { 14125 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId)); 14126 } catch (Resources.NotFoundException e) { 14127 if (android.view.autofill.Helper.sVerbose) { 14128 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id " 14129 + mHintId + ": " + e.getMessage()); 14130 } 14131 } 14132 } 14133 structure.setHint(getHint()); 14134 structure.setInputType(getInputType()); 14135 } 14136 14137 /** 14138 * @hide 14139 */ 14140 @VisibleForTesting canRequestAutofill()14141 public boolean canRequestAutofill() { 14142 if (!isAutofillable()) { 14143 return false; 14144 } 14145 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 14146 if (afm != null) { 14147 return afm.isEnabled(); 14148 } 14149 return false; 14150 } 14151 requestAutofill()14152 private void requestAutofill() { 14153 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 14154 if (afm != null) { 14155 afm.requestAutofill(this); 14156 } 14157 } 14158 14159 @Override autofill(AutofillValue value)14160 public void autofill(AutofillValue value) { 14161 if (android.view.autofill.Helper.sVerbose) { 14162 Log.v(LOG_TAG, "autofill() called on textview for id:" + getAutofillId()); 14163 } 14164 if (!isTextAutofillable()) { 14165 Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this); 14166 return; 14167 } 14168 if (!value.isText()) { 14169 Log.w(LOG_TAG, "value of type " + value.describeContents() 14170 + " cannot be autofilled into " + this); 14171 return; 14172 } 14173 final ClipData clip = ClipData.newPlainText("", value.getTextValue()); 14174 final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build(); 14175 performReceiveContent(payload); 14176 } 14177 14178 @Override getAutofillType()14179 public @AutofillType int getAutofillType() { 14180 return isTextAutofillable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; 14181 } 14182 14183 /** 14184 * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K 14185 * {@code char}s if longer. 14186 * 14187 * @return current text, {@code null} if the text is not editable 14188 * 14189 * @see View#getAutofillValue() 14190 */ 14191 @Override 14192 @Nullable getAutofillValue()14193 public AutofillValue getAutofillValue() { 14194 if (isTextAutofillable()) { 14195 final CharSequence text = TextUtils.trimToParcelableSize(getText()); 14196 return AutofillValue.forText(text); 14197 } 14198 return null; 14199 } 14200 14201 /** @hide */ 14202 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)14203 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 14204 super.onInitializeAccessibilityEventInternal(event); 14205 14206 final boolean isPassword = hasPasswordTransformationMethod(); 14207 event.setPassword(isPassword); 14208 14209 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 14210 event.setFromIndex(Selection.getSelectionStart(mText)); 14211 event.setToIndex(Selection.getSelectionEnd(mText)); 14212 event.setItemCount(mText.length()); 14213 } 14214 } 14215 14216 /** @hide */ 14217 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)14218 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 14219 super.onInitializeAccessibilityNodeInfoInternal(info); 14220 14221 final boolean isPassword = hasPasswordTransformationMethod(); 14222 info.setPassword(isPassword); 14223 info.setText(getTextForAccessibility()); 14224 info.setHintText(mHint); 14225 info.setShowingHintText(isShowingHint()); 14226 14227 if (mBufferType == BufferType.EDITABLE) { 14228 info.setEditable(true); 14229 if (isEnabled()) { 14230 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT); 14231 } 14232 } 14233 14234 if (mEditor != null) { 14235 info.setInputType(mEditor.mInputType); 14236 14237 if (mEditor.mError != null) { 14238 info.setContentInvalid(true); 14239 info.setError(mEditor.mError); 14240 } 14241 // TextView will expose this action if it is editable and has focus. 14242 if (isTextEditable() && isFocused()) { 14243 CharSequence imeActionLabel = mContext.getResources().getString( 14244 com.android.internal.R.string.keyboardview_keycode_enter); 14245 if (getImeActionLabel() != null) { 14246 imeActionLabel = getImeActionLabel(); 14247 } 14248 AccessibilityNodeInfo.AccessibilityAction action = 14249 new AccessibilityNodeInfo.AccessibilityAction( 14250 R.id.accessibilityActionImeEnter, imeActionLabel); 14251 info.addAction(action); 14252 } 14253 } 14254 14255 if (!TextUtils.isEmpty(mText)) { 14256 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 14257 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 14258 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 14259 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 14260 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 14261 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 14262 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 14263 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 14264 if (a11yCharacterInWindowApi()) { 14265 info.setAvailableExtraData(ACCESSIBILITY_EXTRA_DATA_KEYS_FLAGGED); 14266 } else { 14267 info.setAvailableExtraData(ACCESSIBILITY_EXTRA_DATA_KEYS); 14268 } 14269 info.setTextSelectable(isTextSelectable() || isTextEditable()); 14270 } else { 14271 info.setAvailableExtraData(Arrays.asList( 14272 EXTRA_DATA_RENDERING_INFO_KEY 14273 )); 14274 } 14275 14276 if (isFocused()) { 14277 if (canCopy()) { 14278 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 14279 } 14280 if (canPaste()) { 14281 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 14282 } 14283 if (canCut()) { 14284 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 14285 } 14286 if (canReplace()) { 14287 info.addAction( 14288 AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TEXT_SUGGESTIONS); 14289 } 14290 if (canShare()) { 14291 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 14292 ACCESSIBILITY_ACTION_SHARE, 14293 getResources().getString(com.android.internal.R.string.share))); 14294 } 14295 if (canProcessText()) { // also implies mEditor is not null. 14296 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); 14297 mEditor.onInitializeSmartActionsAccessibilityNodeInfo(info); 14298 } 14299 } 14300 14301 // Check for known input filter types. 14302 final int numFilters = mFilters.length; 14303 for (int i = 0; i < numFilters; i++) { 14304 final InputFilter filter = mFilters[i]; 14305 if (filter instanceof InputFilter.LengthFilter) { 14306 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 14307 } 14308 } 14309 14310 if (!isSingleLine()) { 14311 info.setMultiLine(true); 14312 } 14313 14314 // A view should not be exposed as clickable/long-clickable to a service because of a 14315 // LinkMovementMethod or because it has selectable and non-editable text. 14316 if ((info.isClickable() || info.isLongClickable()) 14317 && (mMovement instanceof LinkMovementMethod 14318 || (isTextSelectable() && !isTextEditable()))) { 14319 if (!hasOnClickListeners()) { 14320 info.setClickable(false); 14321 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); 14322 } 14323 if (!hasOnLongClickListeners()) { 14324 info.setLongClickable(false); 14325 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); 14326 } 14327 } 14328 } 14329 14330 @Override addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)14331 public void addExtraDataToAccessibilityNodeInfo( 14332 AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { 14333 boolean isCharacterLocationKey = extraDataKey.equals( 14334 EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY); 14335 boolean isCharacterLocationInWindowKey = (a11yCharacterInWindowApi() && extraDataKey.equals( 14336 EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY)); 14337 if (arguments != null && (isCharacterLocationKey || isCharacterLocationInWindowKey)) { 14338 int positionInfoStartIndex = arguments.getInt( 14339 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); 14340 int positionInfoLength = arguments.getInt( 14341 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1); 14342 if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0) 14343 || (positionInfoStartIndex >= mText.length())) { 14344 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations"); 14345 return; 14346 } 14347 RectF[] boundingRects = new RectF[positionInfoLength]; 14348 final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); 14349 populateCharacterBounds(builder, positionInfoStartIndex, 14350 Math.min(positionInfoStartIndex + positionInfoLength, length()), 14351 viewportToContentHorizontalOffset(), viewportToContentVerticalOffset()); 14352 CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build(); 14353 for (int i = 0; i < positionInfoLength; i++) { 14354 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i); 14355 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) { 14356 RectF bounds = cursorAnchorInfo 14357 .getCharacterBounds(positionInfoStartIndex + i); 14358 if (bounds != null) { 14359 if (isCharacterLocationKey) { 14360 mapRectFromViewToScreenCoords(bounds, true); 14361 } else if (isCharacterLocationInWindowKey) { 14362 mapRectFromViewToWindowCoords(bounds, true); 14363 } 14364 boundingRects[i] = bounds; 14365 } 14366 } 14367 } 14368 info.getExtras().putParcelableArray(extraDataKey, boundingRects); 14369 return; 14370 } 14371 if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) { 14372 final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo = 14373 AccessibilityNodeInfo.ExtraRenderingInfo.obtain(); 14374 extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height); 14375 extraRenderingInfo.setTextSizeInPx(getTextSize()); 14376 extraRenderingInfo.setTextSizeUnit(getTextSizeUnit()); 14377 info.setExtraRenderingInfo(extraRenderingInfo); 14378 } 14379 } 14380 14381 /** 14382 * Don't use, it returns wrong result when the view is scaled. This method can be removed once 14383 * Flags.handwritingGestureWithTransformation is enabled. 14384 * Assume 14385 * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates. 14386 * This method obtains the view's visible rectangle whereas the method 14387 * {@link #getContentVisibleRect} returns the text layout's visible rectangle. 14388 * 14389 * @return true if at least part of the text content is visible; false if the text content is 14390 * completely clipped or translated out of the visible area. 14391 */ getViewVisibleRect(Rect rect)14392 private boolean getViewVisibleRect(Rect rect) { 14393 if (!getLocalVisibleRect(rect)) { 14394 return false; 14395 } 14396 // getLocalVisibleRect returns a rect relative to the unscrolled left top corner of the 14397 // view. In other words, the returned rectangle's origin point is (-scrollX, -scrollY) in 14398 // view's coordinates. So we need to offset it with the negative scrolled amount to convert 14399 // it to view's coordinate. 14400 rect.offset(-getScrollX(), -getScrollY()); 14401 return true; 14402 } 14403 14404 /** 14405 * Don't use, it returns wrong result when view is scaled. This method can be removed once 14406 * Flags.handwritingGestureWithTransformation is enabled. 14407 * Helper method to set {@code rect} to the text content's non-clipped area in the view's 14408 * coordinates. 14409 * 14410 * @return true if at least part of the text content is visible; false if the text content is 14411 * completely clipped or translated out of the visible area. 14412 */ getContentVisibleRect(Rect rect)14413 private boolean getContentVisibleRect(Rect rect) { 14414 if (!getViewVisibleRect(rect)) { 14415 return false; 14416 } 14417 // Clip the view's visible rect with the text layout's visible rect. 14418 return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(), 14419 getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom()); 14420 } 14421 getEditorAndHandwritingBounds(@onNull RectF editorBounds, @Nullable RectF handwritingBounds)14422 private boolean getEditorAndHandwritingBounds(@NonNull RectF editorBounds, 14423 @Nullable RectF handwritingBounds) { 14424 if (mTempRect == null) { 14425 mTempRect = new Rect(); 14426 } 14427 Rect rect = mTempRect; 14428 if (!getGlobalVisibleRect(rect)) { 14429 return false; 14430 } 14431 if (mTempMatrix == null) { 14432 mTempMatrix = new Matrix(); 14433 } 14434 14435 Matrix matrix = mTempMatrix; 14436 matrix.reset(); 14437 transformMatrixRootToLocal(matrix); 14438 editorBounds.set(rect); 14439 // When the view has transformations like scaleX/scaleY computing the global visible 14440 // rectangle will already apply the transformations. The getLocalVisibleRect only offsets 14441 // the global rectangle to local. And the result is wrong the View is scaled. 14442 // 14443 // This approach use the local transformation matrix to map the global rectangle to 14444 // local instead. 14445 // 14446 // Note: it doesn't work well with rotation. Because Rect must be 14447 // axis-aligned, when a rotated Rect becomes quadrilateral, the quadrilateral's 14448 // bounding box is stored at Rect instead. It makes the returned Rect larger than 14449 // the correct size. 14450 matrix.mapRect(editorBounds); 14451 14452 if (handwritingBounds != null) { 14453 // Similar to editorBounds, handwritingBounds must be computed in global coordinates 14454 // and then converted back to local coordinates. Otherwise, if the view is scaled, 14455 // the handwritingBoundsOffsets are also scaled, which is not the expected behavior. 14456 handwritingBounds.top = rect.top - getHandwritingBoundsOffsetTop(); 14457 handwritingBounds.left = rect.left - getHandwritingBoundsOffsetLeft(); 14458 handwritingBounds.bottom = rect.bottom + getHandwritingBoundsOffsetBottom(); 14459 handwritingBounds.right = rect.right + getHandwritingBoundsOffsetRight(); 14460 matrix.mapRect(handwritingBounds); 14461 } 14462 return true; 14463 } 14464 getContentVisibleRect(RectF rect)14465 private boolean getContentVisibleRect(RectF rect) { 14466 if (!getEditorAndHandwritingBounds(rect, /* handwritingBounds= */null)) { 14467 return false; 14468 } 14469 // Clip the view's visible rect with the text layout's visible rect. 14470 return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(), 14471 getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom()); 14472 } 14473 14474 /** 14475 * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} 14476 * 14477 * @param builder The builder to populate 14478 * @param startIndex The starting character index to populate 14479 * @param endIndex The ending character index to populate 14480 * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the 14481 * content 14482 * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content 14483 * @hide 14484 */ populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)14485 public void populateCharacterBounds(CursorAnchorInfo.Builder builder, 14486 int startIndex, int endIndex, float viewportToContentHorizontalOffset, 14487 float viewportToContentVerticalOffset) { 14488 if (isOffsetMappingAvailable()) { 14489 // The text is transformed, and has different length, we don't support 14490 // character bounds in this case yet. 14491 return; 14492 } 14493 final RectF visibleRect = new RectF(); 14494 14495 if (Flags.handwritingGestureWithTransformation()) { 14496 getContentVisibleRect(visibleRect); 14497 } else { 14498 final Rect rect = new Rect(); 14499 getContentVisibleRect(rect); 14500 visibleRect.set(rect); 14501 } 14502 14503 final float[] characterBounds = getCharacterBounds(startIndex, endIndex, 14504 viewportToContentHorizontalOffset, viewportToContentVerticalOffset); 14505 final int limit = endIndex - startIndex; 14506 for (int offset = 0; offset < limit; ++offset) { 14507 final float left = characterBounds[offset * 4]; 14508 final float top = characterBounds[offset * 4 + 1]; 14509 final float right = characterBounds[offset * 4 + 2]; 14510 final float bottom = characterBounds[offset * 4 + 3]; 14511 14512 final boolean hasVisibleRegion = visibleRect.intersects(left, top, right, bottom); 14513 final boolean hasInVisibleRegion = !visibleRect.contains(left, top, right, bottom); 14514 int characterBoundsFlags = 0; 14515 if (hasVisibleRegion) { 14516 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; 14517 } 14518 if (hasInVisibleRegion) { 14519 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 14520 } 14521 14522 if (mLayout.isRtlCharAt(offset)) { 14523 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; 14524 } 14525 builder.addCharacterBounds(offset + startIndex, left, top, right, bottom, 14526 characterBoundsFlags); 14527 } 14528 } 14529 14530 /** 14531 * Return the bounds of the characters in the given range, in TextView's coordinates. 14532 * 14533 * @param start the start index of the interested text range, inclusive. 14534 * @param end the end index of the interested text range, exclusive. 14535 * @param layoutLeft the left of the given {@code layout} in the editor view's coordinates. 14536 * @param layoutTop the top of the given {@code layout} in the editor view's coordinates. 14537 * @return the character bounds stored in a flattened array, in the editor view's coordinates. 14538 */ getCharacterBounds(int start, int end, float layoutLeft, float layoutTop)14539 private float[] getCharacterBounds(int start, int end, float layoutLeft, float layoutTop) { 14540 final float[] characterBounds = new float[4 * (end - start)]; 14541 mLayout.fillCharacterBounds(start, end, characterBounds, 0); 14542 for (int offset = 0; offset < end - start; ++offset) { 14543 characterBounds[4 * offset] += layoutLeft; 14544 characterBounds[4 * offset + 1] += layoutTop; 14545 characterBounds[4 * offset + 2] += layoutLeft; 14546 characterBounds[4 * offset + 3] += layoutTop; 14547 } 14548 return characterBounds; 14549 } 14550 14551 /** 14552 * Compute {@link CursorAnchorInfo} from this {@link TextView}. 14553 * 14554 * @param filter the {@link CursorAnchorInfo} update filter which specified the needed 14555 * information from IME. 14556 * @param cursorAnchorInfoBuilder a cached {@link CursorAnchorInfo.Builder} object used to build 14557 * the result {@link CursorAnchorInfo}. 14558 * @param viewToScreenMatrix a cached {@link Matrix} object used to compute the view to screen 14559 * matrix. 14560 * @return the result {@link CursorAnchorInfo} to be passed to IME. 14561 * @hide 14562 */ 14563 @VisibleForTesting 14564 @Nullable getCursorAnchorInfo(@nputConnection.CursorUpdateFilter int filter, @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder, @NonNull Matrix viewToScreenMatrix)14565 public CursorAnchorInfo getCursorAnchorInfo(@InputConnection.CursorUpdateFilter int filter, 14566 @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder, 14567 @NonNull Matrix viewToScreenMatrix) { 14568 Layout layout = getLayout(); 14569 if (layout == null) { 14570 return null; 14571 } 14572 boolean includeEditorBounds = 14573 (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0; 14574 boolean includeCharacterBounds = 14575 (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0; 14576 boolean includeInsertionMarker = 14577 (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0; 14578 boolean includeVisibleLineBounds = 14579 (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0; 14580 boolean includeTextAppearance = 14581 (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0; 14582 boolean includeAll = 14583 (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker 14584 && !includeVisibleLineBounds && !includeTextAppearance); 14585 14586 includeEditorBounds |= includeAll; 14587 includeCharacterBounds |= includeAll; 14588 includeInsertionMarker |= includeAll; 14589 includeVisibleLineBounds |= includeAll; 14590 includeTextAppearance |= includeAll; 14591 14592 final CursorAnchorInfo.Builder builder = cursorAnchorInfoBuilder; 14593 builder.reset(); 14594 14595 final int selectionStart = getSelectionStart(); 14596 builder.setSelectionRange(selectionStart, getSelectionEnd()); 14597 14598 // Construct transformation matrix from view local coordinates to screen coordinates. 14599 viewToScreenMatrix.reset(); 14600 transformMatrixToGlobal(viewToScreenMatrix); 14601 builder.setMatrix(viewToScreenMatrix); 14602 14603 if (includeEditorBounds) { 14604 final RectF editorBounds = new RectF(); 14605 final RectF handwritingBounds = new RectF(); 14606 if (Flags.handwritingGestureWithTransformation()) { 14607 getEditorAndHandwritingBounds(editorBounds, handwritingBounds); 14608 } else { 14609 if (mTempRect == null) { 14610 mTempRect = new Rect(); 14611 } 14612 final Rect bounds = mTempRect; 14613 14614 // If the editor is not visible at all, return empty rectangles. We still need to 14615 // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo. 14616 if (getViewVisibleRect(bounds)) { 14617 editorBounds.set(bounds); 14618 handwritingBounds.set(editorBounds); 14619 handwritingBounds.top -= getHandwritingBoundsOffsetTop(); 14620 handwritingBounds.left -= getHandwritingBoundsOffsetLeft(); 14621 handwritingBounds.bottom += getHandwritingBoundsOffsetBottom(); 14622 handwritingBounds.right += getHandwritingBoundsOffsetRight(); 14623 } 14624 } 14625 EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder(); 14626 EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds) 14627 .setHandwritingBounds(handwritingBounds).build(); 14628 builder.setEditorBoundsInfo(editorBoundsInfo); 14629 } 14630 14631 if (includeCharacterBounds || includeInsertionMarker || includeVisibleLineBounds) { 14632 final float viewportToContentHorizontalOffset = 14633 viewportToContentHorizontalOffset(); 14634 final float viewportToContentVerticalOffset = 14635 viewportToContentVerticalOffset(); 14636 final boolean isTextTransformed = (getTransformationMethod() != null 14637 && getTransformed() instanceof OffsetMapping); 14638 if (includeCharacterBounds && !isTextTransformed) { 14639 final CharSequence text = getText(); 14640 if (text instanceof Spannable) { 14641 final Spannable sp = (Spannable) text; 14642 int composingTextStart = EditableInputConnection.getComposingSpanStart(sp); 14643 int composingTextEnd = EditableInputConnection.getComposingSpanEnd(sp); 14644 if (composingTextEnd < composingTextStart) { 14645 final int temp = composingTextEnd; 14646 composingTextEnd = composingTextStart; 14647 composingTextStart = temp; 14648 } 14649 final boolean hasComposingText = 14650 (0 <= composingTextStart) && (composingTextStart 14651 < composingTextEnd); 14652 if (hasComposingText) { 14653 final CharSequence composingText = text.subSequence(composingTextStart, 14654 composingTextEnd); 14655 builder.setComposingText(composingTextStart, composingText); 14656 populateCharacterBounds(builder, composingTextStart, 14657 composingTextEnd, viewportToContentHorizontalOffset, 14658 viewportToContentVerticalOffset); 14659 } 14660 } 14661 } 14662 14663 if (includeInsertionMarker) { 14664 // Treat selectionStart as the insertion point. 14665 if (0 <= selectionStart) { 14666 final int offsetTransformed = originalToTransformed( 14667 selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR); 14668 final int line = layout.getLineForOffset(offsetTransformed); 14669 final float insertionMarkerX = 14670 layout.getPrimaryHorizontal( 14671 offsetTransformed, layout.shouldClampCursor(line)) 14672 + viewportToContentHorizontalOffset; 14673 final float insertionMarkerTop = layout.getLineTop(line) 14674 + viewportToContentVerticalOffset; 14675 final float insertionMarkerBaseline = layout.getLineBaseline(line) 14676 + viewportToContentVerticalOffset; 14677 final float insertionMarkerBottom = 14678 layout.getLineBottom(line, /* includeLineSpacing= */ false) 14679 + viewportToContentVerticalOffset; 14680 final boolean isTopVisible = 14681 isPositionVisible(insertionMarkerX, insertionMarkerTop); 14682 final boolean isBottomVisible = 14683 isPositionVisible(insertionMarkerX, insertionMarkerBottom); 14684 int insertionMarkerFlags = 0; 14685 if (isTopVisible || isBottomVisible) { 14686 insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; 14687 } 14688 if (!isTopVisible || !isBottomVisible) { 14689 insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 14690 } 14691 if (layout.isRtlCharAt(offsetTransformed)) { 14692 insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL; 14693 } 14694 builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop, 14695 insertionMarkerBaseline, insertionMarkerBottom, 14696 insertionMarkerFlags); 14697 } 14698 } 14699 14700 if (includeVisibleLineBounds) { 14701 if (Flags.handwritingGestureWithTransformation()) { 14702 RectF visibleRect = new RectF(); 14703 if (getContentVisibleRect(visibleRect)) { 14704 // Subtract the viewportToContentVerticalOffset to convert the view 14705 // coordinates to layout coordinates. 14706 final float visibleTop = 14707 visibleRect.top - viewportToContentVerticalOffset; 14708 final float visibleBottom = 14709 visibleRect.bottom - viewportToContentVerticalOffset; 14710 final int firstLine = 14711 layout.getLineForVertical((int) Math.floor(visibleTop)); 14712 final int lastLine = 14713 layout.getLineForVertical((int) Math.ceil(visibleBottom)); 14714 14715 for (int line = firstLine; line <= lastLine; ++line) { 14716 final float left = layout.getLineLeft(line) 14717 + viewportToContentHorizontalOffset; 14718 final float top = layout.getLineTop(line) 14719 + viewportToContentVerticalOffset; 14720 final float right = layout.getLineRight(line) 14721 + viewportToContentHorizontalOffset; 14722 final float bottom = layout.getLineBottom(line, false) 14723 + viewportToContentVerticalOffset; 14724 builder.addVisibleLineBounds(left, top, right, bottom); 14725 } 14726 } 14727 } else { 14728 final Rect visibleRect = new Rect(); 14729 if (getContentVisibleRect(visibleRect)) { 14730 // Subtract the viewportToContentVerticalOffset to convert the view 14731 // coordinates to layout coordinates. 14732 final float visibleTop = 14733 visibleRect.top - viewportToContentVerticalOffset; 14734 final float visibleBottom = 14735 visibleRect.bottom - viewportToContentVerticalOffset; 14736 final int firstLine = 14737 layout.getLineForVertical((int) Math.floor(visibleTop)); 14738 final int lastLine = 14739 layout.getLineForVertical((int) Math.ceil(visibleBottom)); 14740 14741 for (int line = firstLine; line <= lastLine; ++line) { 14742 final float left = layout.getLineLeft(line) 14743 + viewportToContentHorizontalOffset; 14744 final float top = layout.getLineTop(line) 14745 + viewportToContentVerticalOffset; 14746 final float right = layout.getLineRight(line) 14747 + viewportToContentHorizontalOffset; 14748 final float bottom = layout.getLineBottom(line, false) 14749 + viewportToContentVerticalOffset; 14750 builder.addVisibleLineBounds(left, top, right, bottom); 14751 } 14752 } 14753 } 14754 } 14755 } 14756 14757 if (includeTextAppearance) { 14758 builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(this)); 14759 } 14760 return builder.build(); 14761 } 14762 14763 /** 14764 * Creates the {@link TextBoundsInfo} for the text lines that intersects with the {@code rectF}. 14765 * @hide 14766 */ getTextBoundsInfo(@onNull RectF bounds)14767 public TextBoundsInfo getTextBoundsInfo(@NonNull RectF bounds) { 14768 final Layout layout = getLayout(); 14769 if (layout == null) { 14770 // No valid text layout, return null. 14771 return null; 14772 } 14773 final CharSequence text = layout.getText(); 14774 if (text == null || isOffsetMappingAvailable()) { 14775 // The text is Null or the text has been transformed. Can't provide TextBoundsInfo. 14776 return null; 14777 } 14778 14779 final Matrix localToGlobalMatrix = new Matrix(); 14780 transformMatrixToGlobal(localToGlobalMatrix); 14781 final Matrix globalToLocalMatrix = new Matrix(); 14782 if (!localToGlobalMatrix.invert(globalToLocalMatrix)) { 14783 // Can't map global rectF to local coordinates, this is almost impossible in practice. 14784 return null; 14785 } 14786 14787 final float layoutLeft = viewportToContentHorizontalOffset(); 14788 final float layoutTop = viewportToContentVerticalOffset(); 14789 14790 final RectF localBounds = new RectF(bounds); 14791 globalToLocalMatrix.mapRect(localBounds); 14792 localBounds.offset(-layoutLeft, -layoutTop); 14793 14794 // Text length is 0. There is no character bounds, return empty TextBoundsInfo. 14795 // rectF doesn't intersect with the layout, return empty TextBoundsInfo. 14796 if (!localBounds.intersects(0f, 0f, layout.getWidth(), layout.getHeight()) 14797 || text.length() == 0) { 14798 final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(0, 0); 14799 final SegmentFinder emptySegmentFinder = 14800 new SegmentFinder.PrescribedSegmentFinder(new int[0]); 14801 builder.setMatrix(localToGlobalMatrix) 14802 .setCharacterBounds(new float[0]) 14803 .setCharacterBidiLevel(new int[0]) 14804 .setCharacterFlags(new int[0]) 14805 .setGraphemeSegmentFinder(emptySegmentFinder) 14806 .setLineSegmentFinder(emptySegmentFinder) 14807 .setWordSegmentFinder(emptySegmentFinder); 14808 return builder.build(); 14809 } 14810 14811 final int startLine = layout.getLineForVertical((int) Math.floor(localBounds.top)); 14812 final int endLine = layout.getLineForVertical((int) Math.floor(localBounds.bottom)); 14813 final int start = layout.getLineStart(startLine); 14814 final int end = layout.getLineEnd(endLine); 14815 14816 // Compute character bounds. 14817 final float[] characterBounds = getCharacterBounds(start, end, layoutLeft, layoutTop); 14818 14819 // Compute character flags and BiDi levels. 14820 final int[] characterFlags = new int[end - start]; 14821 final int[] characterBidiLevels = new int[end - start]; 14822 for (int line = startLine; line <= endLine; ++line) { 14823 final int lineStart = layout.getLineStart(line); 14824 final int lineEnd = layout.getLineEnd(line); 14825 final Layout.Directions directions = layout.getLineDirections(line); 14826 for (int i = 0; i < directions.getRunCount(); ++i) { 14827 final int runStart = directions.getRunStart(i) + lineStart; 14828 final int runEnd = Math.min(runStart + directions.getRunLength(i), lineEnd); 14829 final int runLevel = directions.getRunLevel(i); 14830 Arrays.fill(characterBidiLevels, runStart - start, runEnd - start, runLevel); 14831 } 14832 14833 final boolean lineIsRtl = 14834 layout.getParagraphDirection(line) == Layout.DIR_RIGHT_TO_LEFT; 14835 for (int index = lineStart; index < lineEnd; ++index) { 14836 int flags = 0; 14837 if (TextUtils.isWhitespace(text.charAt(index))) { 14838 flags |= TextBoundsInfo.FLAG_CHARACTER_WHITESPACE; 14839 } 14840 if (TextUtils.isPunctuation(Character.codePointAt(text, index))) { 14841 flags |= TextBoundsInfo.FLAG_CHARACTER_PUNCTUATION; 14842 } 14843 if (TextUtils.isNewline(Character.codePointAt(text, index))) { 14844 flags |= TextBoundsInfo.FLAG_CHARACTER_LINEFEED; 14845 } 14846 if (lineIsRtl) { 14847 flags |= TextBoundsInfo.FLAG_LINE_IS_RTL; 14848 } 14849 characterFlags[index - start] = flags; 14850 } 14851 } 14852 14853 // Create grapheme SegmentFinder. 14854 final SegmentFinder graphemeSegmentFinder = 14855 new GraphemeClusterSegmentFinder(text, layout.getPaint()); 14856 14857 // Create word SegmentFinder. 14858 final WordIterator wordIterator = getWordIterator(); 14859 wordIterator.setCharSequence(text, 0, text.length()); 14860 final SegmentFinder wordSegmentFinder = new WordSegmentFinder(text, wordIterator); 14861 14862 // Create line SegmentFinder. 14863 final int lineCount = endLine - startLine + 1; 14864 final int[] lineRanges = new int[2 * lineCount]; 14865 for (int line = startLine; line <= endLine; ++line) { 14866 final int offset = line - startLine; 14867 lineRanges[2 * offset] = layout.getLineStart(line); 14868 lineRanges[2 * offset + 1] = layout.getLineEnd(line); 14869 } 14870 final SegmentFinder lineSegmentFinder = 14871 new SegmentFinder.PrescribedSegmentFinder(lineRanges); 14872 14873 return new TextBoundsInfo.Builder(start, end) 14874 .setMatrix(localToGlobalMatrix) 14875 .setCharacterBounds(characterBounds) 14876 .setCharacterBidiLevel(characterBidiLevels) 14877 .setCharacterFlags(characterFlags) 14878 .setGraphemeSegmentFinder(graphemeSegmentFinder) 14879 .setLineSegmentFinder(lineSegmentFinder) 14880 .setWordSegmentFinder(wordSegmentFinder) 14881 .build(); 14882 } 14883 14884 /** 14885 * @hide 14886 */ isPositionVisible(final float positionX, final float positionY)14887 public boolean isPositionVisible(final float positionX, final float positionY) { 14888 synchronized (TEMP_POSITION) { 14889 final float[] position = TEMP_POSITION; 14890 position[0] = positionX; 14891 position[1] = positionY; 14892 View view = this; 14893 14894 while (view != null) { 14895 if (view != this) { 14896 // Local scroll is already taken into account in positionX/Y 14897 position[0] -= view.getScrollX(); 14898 position[1] -= view.getScrollY(); 14899 } 14900 14901 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth() 14902 || position[1] > view.getHeight()) { 14903 return false; 14904 } 14905 14906 if (!view.getMatrix().isIdentity()) { 14907 view.getMatrix().mapPoints(position); 14908 } 14909 14910 position[0] += view.getLeft(); 14911 position[1] += view.getTop(); 14912 14913 final ViewParent parent = view.getParent(); 14914 if (parent instanceof View) { 14915 view = (View) parent; 14916 } else { 14917 // We've reached the ViewRoot, stop iterating 14918 view = null; 14919 } 14920 } 14921 } 14922 14923 // We've been able to walk up the view hierarchy and the position was never clipped 14924 return true; 14925 } 14926 14927 /** 14928 * Performs an accessibility action after it has been offered to the 14929 * delegate. 14930 * 14931 * @hide 14932 */ 14933 @Override performAccessibilityActionInternal(int action, Bundle arguments)14934 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 14935 if (mEditor != null) { 14936 if (mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action) 14937 || mEditor.performSmartActionsAccessibilityAction(action)) { 14938 return true; 14939 } 14940 } 14941 switch (action) { 14942 case AccessibilityNodeInfo.ACTION_CLICK: { 14943 return performAccessibilityActionClick(arguments); 14944 } 14945 case AccessibilityNodeInfo.ACTION_COPY: { 14946 if (isFocused() && canCopy()) { 14947 if (onTextContextMenuItem(ID_COPY)) { 14948 return true; 14949 } 14950 } 14951 } return false; 14952 case AccessibilityNodeInfo.ACTION_PASTE: { 14953 if (isFocused() && canPaste()) { 14954 if (onTextContextMenuItem(ID_PASTE)) { 14955 return true; 14956 } 14957 } 14958 } return false; 14959 case AccessibilityNodeInfo.ACTION_CUT: { 14960 if (isFocused() && canCut()) { 14961 if (onTextContextMenuItem(ID_CUT)) { 14962 return true; 14963 } 14964 } 14965 } return false; 14966 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 14967 ensureIterableTextForAccessibilitySelectable(); 14968 CharSequence text = getIterableTextForAccessibility(); 14969 if (text == null) { 14970 return false; 14971 } 14972 final int start = (arguments != null) ? arguments.getInt( 14973 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 14974 final int end = (arguments != null) ? arguments.getInt( 14975 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 14976 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 14977 // No arguments clears the selection. 14978 if (start == end && end == -1) { 14979 Selection.removeSelection((Spannable) text); 14980 return true; 14981 } 14982 if (start >= 0 && start <= end && end <= text.length()) { 14983 requestFocusOnNonEditableSelectableText(); 14984 Selection.setSelection((Spannable) text, start, end); 14985 // Make sure selection mode is engaged. 14986 if (mEditor != null) { 14987 mEditor.startSelectionActionModeAsync(false); 14988 } 14989 return true; 14990 } 14991 } 14992 } return false; 14993 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 14994 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 14995 ensureIterableTextForAccessibilitySelectable(); 14996 return super.performAccessibilityActionInternal(action, arguments); 14997 } 14998 case ACCESSIBILITY_ACTION_SHARE: { 14999 if (isFocused() && canShare()) { 15000 if (onTextContextMenuItem(ID_SHARE)) { 15001 return true; 15002 } 15003 } 15004 } return false; 15005 case AccessibilityNodeInfo.ACTION_SET_TEXT: { 15006 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) { 15007 return false; 15008 } 15009 CharSequence text = (arguments != null) ? arguments.getCharSequence( 15010 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; 15011 setText(text); 15012 if (mText != null) { 15013 int updatedTextLength = mText.length(); 15014 if (updatedTextLength > 0) { 15015 Selection.setSelection(mSpannable, updatedTextLength); 15016 } 15017 } 15018 } return true; 15019 case R.id.accessibilityActionImeEnter: { 15020 if (isFocused() && isTextEditable()) { 15021 onEditorAction(getImeActionId()); 15022 } 15023 } return true; 15024 case AccessibilityNodeInfo.ACTION_LONG_CLICK: { 15025 if (isLongClickable()) { 15026 boolean handled; 15027 if (isEnabled() && (mBufferType == BufferType.EDITABLE)) { 15028 mEditor.mIsBeingLongClickedByAccessibility = true; 15029 try { 15030 handled = performLongClick(); 15031 } finally { 15032 mEditor.mIsBeingLongClickedByAccessibility = false; 15033 } 15034 } else { 15035 handled = performLongClick(); 15036 } 15037 return handled; 15038 } 15039 } 15040 return false; 15041 default: { 15042 // New ids have static blocks to assign values, so they can't be used in a case 15043 // block. 15044 if (action == R.id.accessibilityActionShowTextSuggestions) { 15045 return isFocused() && canReplace() && onTextContextMenuItem(ID_REPLACE); 15046 } 15047 return super.performAccessibilityActionInternal(action, arguments); 15048 } 15049 } 15050 } 15051 performAccessibilityActionClick(Bundle arguments)15052 private boolean performAccessibilityActionClick(Bundle arguments) { 15053 boolean handled = false; 15054 15055 if (!isEnabled()) { 15056 return false; 15057 } 15058 15059 if (isClickable() || isLongClickable()) { 15060 // Simulate View.onTouchEvent for an ACTION_UP event 15061 if (isFocusable() && !isFocused()) { 15062 requestFocus(); 15063 } 15064 15065 performClick(); 15066 handled = true; 15067 } 15068 15069 // Show the IME, except when selecting in read-only text. 15070 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null 15071 && (isTextEditable() || isTextSelectable()) && isFocused()) { 15072 final InputMethodManager imm = getInputMethodManager(); 15073 viewClicked(imm); 15074 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 15075 handled |= imm.showSoftInput(this, 0); 15076 } 15077 } 15078 15079 return handled; 15080 } 15081 requestFocusOnNonEditableSelectableText()15082 private void requestFocusOnNonEditableSelectableText() { 15083 if (!isTextEditable() && isTextSelectable()) { 15084 if (!isEnabled()) { 15085 return; 15086 } 15087 15088 if (isFocusable() && !isFocused()) { 15089 requestFocus(); 15090 } 15091 } 15092 } 15093 hasSpannableText()15094 private boolean hasSpannableText() { 15095 return mText != null && mText instanceof Spannable; 15096 } 15097 15098 /** @hide */ 15099 @Override sendAccessibilityEventInternal(int eventType)15100 public void sendAccessibilityEventInternal(int eventType) { 15101 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) { 15102 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions(); 15103 } 15104 15105 super.sendAccessibilityEventInternal(eventType); 15106 } 15107 15108 @Override sendAccessibilityEventUnchecked(AccessibilityEvent event)15109 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { 15110 // Do not send scroll events since first they are not interesting for 15111 // accessibility and second such events a generated too frequently. 15112 // For details see the implementation of bringTextIntoView(). 15113 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 15114 return; 15115 } 15116 super.sendAccessibilityEventUnchecked(event); 15117 } 15118 15119 /** 15120 * Returns the text that should be exposed to accessibility services. 15121 * <p> 15122 * This approximates what is displayed visually. 15123 * 15124 * @return the text that should be exposed to accessibility services, may 15125 * be {@code null} if no text is set 15126 */ 15127 @Nullable 15128 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getTextForAccessibility()15129 private CharSequence getTextForAccessibility() { 15130 // If the text is empty, we must be showing the hint text. 15131 if (TextUtils.isEmpty(mText)) { 15132 return mHint; 15133 } 15134 15135 // Otherwise, return whatever text is being displayed. 15136 return TextUtils.trimToParcelableSize(mTransformed); 15137 } 15138 isVisibleToAccessibility()15139 boolean isVisibleToAccessibility() { 15140 return AccessibilityManager.getInstance(mContext).isEnabled() 15141 && (isFocused() || (isSelected() && isShown())); 15142 } 15143 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)15144 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 15145 int fromIndex, int removedCount, int addedCount) { 15146 AccessibilityEvent event = 15147 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 15148 event.setFromIndex(fromIndex); 15149 event.setRemovedCount(removedCount); 15150 event.setAddedCount(addedCount); 15151 event.setBeforeText(beforeText); 15152 sendAccessibilityEventUnchecked(event); 15153 } 15154 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int toIndex)15155 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 15156 int fromIndex, int toIndex) { 15157 AccessibilityEvent event = 15158 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 15159 event.setFromIndex(fromIndex); 15160 event.setToIndex(toIndex); 15161 event.setBeforeText(beforeText); 15162 sendAccessibilityEventUnchecked(event); 15163 } 15164 getInputMethodManager()15165 private InputMethodManager getInputMethodManager() { 15166 return getContext().getSystemService(InputMethodManager.class); 15167 } 15168 15169 /** 15170 * Returns whether this text view is a current input method target. The 15171 * default implementation just checks with {@link InputMethodManager}. 15172 * @return True if the TextView is a current input method target; false otherwise. 15173 */ isInputMethodTarget()15174 public boolean isInputMethodTarget() { 15175 InputMethodManager imm = getInputMethodManager(); 15176 return imm != null && imm.isActive(this); 15177 } 15178 15179 static final int ID_SELECT_ALL = android.R.id.selectAll; 15180 static final int ID_UNDO = android.R.id.undo; 15181 static final int ID_REDO = android.R.id.redo; 15182 static final int ID_CUT = android.R.id.cut; 15183 static final int ID_COPY = android.R.id.copy; 15184 static final int ID_PASTE = android.R.id.paste; 15185 static final int ID_SHARE = android.R.id.shareText; 15186 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; 15187 static final int ID_REPLACE = android.R.id.replaceText; 15188 static final int ID_ASSIST = android.R.id.textAssist; 15189 static final int ID_AUTOFILL = android.R.id.autofill; 15190 15191 /** 15192 * Called when a context menu option for the text view is selected. Currently 15193 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 15194 * {@link android.R.id#copy}, {@link android.R.id#paste}, 15195 * {@link android.R.id#pasteAsPlainText} (starting at API level 23) or 15196 * {@link android.R.id#shareText}. 15197 * 15198 * @return true if the context menu item action was performed. 15199 */ onTextContextMenuItem(int id)15200 public boolean onTextContextMenuItem(int id) { 15201 int min = 0; 15202 int max = mText.length(); 15203 15204 if (isFocused()) { 15205 final int selStart = getSelectionStart(); 15206 final int selEnd = getSelectionEnd(); 15207 15208 min = Math.max(0, Math.min(selStart, selEnd)); 15209 max = Math.max(0, Math.max(selStart, selEnd)); 15210 } 15211 15212 switch (id) { 15213 case ID_SELECT_ALL: 15214 final boolean hadSelection = hasSelection(); 15215 selectAllText(); 15216 if (mEditor != null && hadSelection) { 15217 mEditor.invalidateActionModeAsync(); 15218 } 15219 return true; 15220 15221 case ID_UNDO: 15222 if (mEditor != null) { 15223 mEditor.undo(); 15224 } 15225 return true; // Returns true even if nothing was undone. 15226 15227 case ID_REDO: 15228 if (mEditor != null) { 15229 mEditor.redo(); 15230 } 15231 return true; // Returns true even if nothing was undone. 15232 15233 case ID_PASTE: 15234 paste(true /* withFormatting */); 15235 return true; 15236 15237 case ID_PASTE_AS_PLAIN_TEXT: 15238 paste(false /* withFormatting */); 15239 return true; 15240 15241 case ID_CUT: 15242 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max)); 15243 if (setPrimaryClip(cutData)) { 15244 deleteText_internal(min, max); 15245 } else { 15246 Toast.makeText(getContext(), 15247 com.android.internal.R.string.failed_to_copy_to_clipboard, 15248 Toast.LENGTH_SHORT).show(); 15249 } 15250 return true; 15251 15252 case ID_COPY: 15253 // For link action mode in a non-selectable/non-focusable TextView, 15254 // make sure that we set the appropriate min/max. 15255 final int selStart = getSelectionStart(); 15256 final int selEnd = getSelectionEnd(); 15257 min = Math.max(0, Math.min(selStart, selEnd)); 15258 max = Math.max(0, Math.max(selStart, selEnd)); 15259 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max)); 15260 if (setPrimaryClip(copyData)) { 15261 stopTextActionMode(); 15262 } else { 15263 Toast.makeText(getContext(), 15264 com.android.internal.R.string.failed_to_copy_to_clipboard, 15265 Toast.LENGTH_SHORT).show(); 15266 } 15267 return true; 15268 15269 case ID_REPLACE: 15270 if (mEditor != null) { 15271 mEditor.replace(); 15272 } 15273 return true; 15274 15275 case ID_SHARE: 15276 shareSelectedText(); 15277 return true; 15278 15279 case ID_AUTOFILL: 15280 requestAutofill(); 15281 stopTextActionMode(); 15282 return true; 15283 } 15284 return false; 15285 } 15286 15287 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getTransformedText(int start, int end)15288 CharSequence getTransformedText(int start, int end) { 15289 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 15290 } 15291 15292 @Override performLongClick()15293 public boolean performLongClick() { 15294 if (DEBUG_CURSOR) { 15295 logCursor("performLongClick", null); 15296 } 15297 15298 boolean handled = false; 15299 boolean performedHapticFeedback = false; 15300 15301 if (mEditor != null) { 15302 mEditor.mIsBeingLongClicked = true; 15303 } 15304 15305 if (super.performLongClick()) { 15306 handled = true; 15307 performedHapticFeedback = true; 15308 } 15309 15310 if (mEditor != null) { 15311 handled |= mEditor.performLongClick(handled); 15312 mEditor.mIsBeingLongClicked = false; 15313 } 15314 15315 if (handled) { 15316 if (!performedHapticFeedback) { 15317 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 15318 } 15319 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 15320 } else { 15321 MetricsLogger.action( 15322 mContext, 15323 MetricsEvent.TEXT_LONGPRESS, 15324 TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER); 15325 } 15326 15327 return handled; 15328 } 15329 15330 @Override onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)15331 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 15332 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 15333 if (mEditor != null) { 15334 mEditor.onScrollChanged(); 15335 } 15336 } 15337 15338 /** 15339 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 15340 * by the IME or by the spell checker as the user types. This is done by adding 15341 * {@link SuggestionSpan}s to the text. 15342 * 15343 * When suggestions are enabled (default), this list of suggestions will be displayed when the 15344 * user asks for them on these parts of the text. This value depends on the inputType of this 15345 * TextView. 15346 * 15347 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 15348 * 15349 * In addition, the type variation must be one of 15350 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 15351 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 15352 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 15353 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 15354 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 15355 * 15356 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 15357 * 15358 * @return true if the suggestions popup window is enabled, based on the inputType. 15359 */ isSuggestionsEnabled()15360 public boolean isSuggestionsEnabled() { 15361 if (mEditor == null) return false; 15362 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 15363 return false; 15364 } 15365 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 15366 15367 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 15368 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL 15369 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT 15370 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE 15371 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE 15372 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 15373 } 15374 15375 /** 15376 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 15377 * selection is initiated in this View. 15378 * 15379 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy, 15380 * Paste, Replace and Share actions, depending on what this View supports. 15381 * 15382 * <p>A custom implementation can add new entries in the default menu in its 15383 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} 15384 * method. The default actions can also be removed from the menu using 15385 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 15386 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, 15387 * {@link android.R.id#pasteAsPlainText} (starting at API level 23), 15388 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. 15389 * 15390 * <p>Returning false from 15391 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} 15392 * will prevent the action mode from being started. 15393 * 15394 * <p>Action click events should be handled by the custom implementation of 15395 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, 15396 * android.view.MenuItem)}. 15397 * 15398 * <p>Note that text selection mode is not started when a TextView receives focus and the 15399 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 15400 * that case, to allow for quick replacement. 15401 */ setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)15402 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 15403 createEditorIfNeeded(); 15404 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 15405 } 15406 15407 /** 15408 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 15409 * 15410 * @return The current custom selection callback. 15411 */ getCustomSelectionActionModeCallback()15412 public ActionMode.Callback getCustomSelectionActionModeCallback() { 15413 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 15414 } 15415 15416 /** 15417 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 15418 * insertion is initiated in this View. 15419 * The standard implementation populates the menu with a subset of Select All, 15420 * Paste and Replace actions, depending on what this View supports. 15421 * 15422 * <p>A custom implementation can add new entries in the default menu in its 15423 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, 15424 * android.view.Menu)} method. The default actions can also be removed from the menu using 15425 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 15426 * {@link android.R.id#paste}, {@link android.R.id#pasteAsPlainText} (starting at API 15427 * level 23) or {@link android.R.id#replaceText} ids as parameters.</p> 15428 * 15429 * <p>Returning false from 15430 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, 15431 * android.view.Menu)} will prevent the action mode from being started.</p> 15432 * 15433 * <p>Action click events should be handled by the custom implementation of 15434 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, 15435 * android.view.MenuItem)}.</p> 15436 * 15437 * <p>Note that text insertion mode is not started when a TextView receives focus and the 15438 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p> 15439 */ setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)15440 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { 15441 createEditorIfNeeded(); 15442 mEditor.mCustomInsertionActionModeCallback = actionModeCallback; 15443 } 15444 15445 /** 15446 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. 15447 * 15448 * @return The current custom insertion callback. 15449 */ getCustomInsertionActionModeCallback()15450 public ActionMode.Callback getCustomInsertionActionModeCallback() { 15451 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; 15452 } 15453 15454 /** 15455 * Sets the {@link TextClassifier} for this TextView. 15456 */ setTextClassifier(@ullable TextClassifier textClassifier)15457 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 15458 mTextClassifier = textClassifier; 15459 } 15460 15461 /** 15462 * Returns the {@link TextClassifier} used by this TextView. 15463 * If no TextClassifier has been set, this TextView uses the default set by the 15464 * {@link TextClassificationManager}. 15465 */ 15466 @NonNull getTextClassifier()15467 public TextClassifier getTextClassifier() { 15468 if (mTextClassifier == null) { 15469 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 15470 if (tcm != null) { 15471 return tcm.getTextClassifier(); 15472 } 15473 return TextClassifier.NO_OP; 15474 } 15475 return mTextClassifier; 15476 } 15477 15478 /** 15479 * Returns a session-aware text classifier. 15480 * This method creates one if none already exists or the current one is destroyed. 15481 */ 15482 @NonNull getTextClassificationSession()15483 TextClassifier getTextClassificationSession() { 15484 if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) { 15485 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 15486 if (tcm != null) { 15487 final String widgetType; 15488 if (isTextEditable()) { 15489 widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT; 15490 } else if (isTextSelectable()) { 15491 widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW; 15492 } else { 15493 widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW; 15494 } 15495 mTextClassificationContext = new TextClassificationContext.Builder( 15496 mContext.getPackageName(), widgetType) 15497 .build(); 15498 if (mTextClassifier != null) { 15499 mTextClassificationSession = tcm.createTextClassificationSession( 15500 mTextClassificationContext, mTextClassifier); 15501 } else { 15502 mTextClassificationSession = tcm.createTextClassificationSession( 15503 mTextClassificationContext); 15504 } 15505 } else { 15506 mTextClassificationSession = TextClassifier.NO_OP; 15507 } 15508 } 15509 return mTextClassificationSession; 15510 } 15511 15512 /** 15513 * Returns the {@link TextClassificationContext} for the current TextClassifier session. 15514 * @see #getTextClassificationSession() 15515 */ 15516 @Nullable getTextClassificationContext()15517 TextClassificationContext getTextClassificationContext() { 15518 return mTextClassificationContext; 15519 } 15520 15521 /** 15522 * Returns true if this TextView uses a no-op TextClassifier. 15523 */ usesNoOpTextClassifier()15524 boolean usesNoOpTextClassifier() { 15525 return getTextClassifier() == TextClassifier.NO_OP; 15526 } 15527 15528 /** 15529 * Starts an ActionMode for the specified TextLinkSpan. 15530 * 15531 * @return Whether or not we're attempting to start the action mode. 15532 * @hide 15533 */ requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)15534 public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) { 15535 Preconditions.checkNotNull(clickedSpan); 15536 15537 if (!(mText instanceof Spanned)) { 15538 return false; 15539 } 15540 15541 final int start = ((Spanned) mText).getSpanStart(clickedSpan); 15542 final int end = ((Spanned) mText).getSpanEnd(clickedSpan); 15543 15544 if (start < 0 || end > mText.length() || start >= end) { 15545 return false; 15546 } 15547 15548 createEditorIfNeeded(); 15549 mEditor.startLinkActionModeAsync(start, end); 15550 return true; 15551 } 15552 15553 /** 15554 * Handles a click on the specified TextLinkSpan. 15555 * 15556 * @return Whether or not the click is being handled. 15557 * @hide 15558 */ handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)15559 public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) { 15560 Preconditions.checkNotNull(clickedSpan); 15561 if (mText instanceof Spanned) { 15562 final Spanned spanned = (Spanned) mText; 15563 final int start = spanned.getSpanStart(clickedSpan); 15564 final int end = spanned.getSpanEnd(clickedSpan); 15565 if (start >= 0 && end <= mText.length() && start < end) { 15566 final TextClassification.Request request = new TextClassification.Request.Builder( 15567 mText, start, end) 15568 .setDefaultLocales(getTextLocales()) 15569 .build(); 15570 final Supplier<TextClassification> supplier = () -> 15571 getTextClassificationSession().classifyText(request); 15572 final Consumer<TextClassification> consumer = classification -> { 15573 if (classification != null) { 15574 if (!classification.getActions().isEmpty()) { 15575 try { 15576 classification.getActions().get(0).getActionIntent().send(); 15577 } catch (PendingIntent.CanceledException e) { 15578 Log.e(LOG_TAG, "Error sending PendingIntent", e); 15579 } 15580 } else { 15581 Log.d(LOG_TAG, "No link action to perform"); 15582 } 15583 } else { 15584 // classification == null 15585 Log.d(LOG_TAG, "Timeout while classifying text"); 15586 } 15587 }; 15588 CompletableFuture.supplyAsync(supplier) 15589 .completeOnTimeout(null, 1, TimeUnit.SECONDS) 15590 .thenAccept(consumer); 15591 return true; 15592 } 15593 } 15594 return false; 15595 } 15596 15597 /** 15598 * @hide 15599 */ 15600 @UnsupportedAppUsage stopTextActionMode()15601 protected void stopTextActionMode() { 15602 if (mEditor != null) { 15603 mEditor.stopTextActionMode(); 15604 } 15605 } 15606 15607 /** @hide */ hideFloatingToolbar(int durationMs)15608 public void hideFloatingToolbar(int durationMs) { 15609 if (mEditor != null) { 15610 mEditor.hideFloatingToolbar(durationMs); 15611 } 15612 } 15613 15614 /** @hide */ 15615 @VisibleForTesting canUndo()15616 public boolean canUndo() { 15617 return mEditor != null && mEditor.canUndo(); 15618 } 15619 15620 /** @hide */ 15621 @VisibleForTesting canRedo()15622 public boolean canRedo() { 15623 return mEditor != null && mEditor.canRedo(); 15624 } 15625 15626 /** @hide */ 15627 @VisibleForTesting canCut()15628 public boolean canCut() { 15629 if (hasPasswordTransformationMethod()) { 15630 return false; 15631 } 15632 15633 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null 15634 && mEditor.mKeyListener != null) { 15635 return true; 15636 } 15637 15638 return false; 15639 } 15640 15641 /** @hide */ 15642 @VisibleForTesting canCopy()15643 public boolean canCopy() { 15644 if (hasPasswordTransformationMethod()) { 15645 return false; 15646 } 15647 15648 if (mText.length() > 0 && hasSelection() && mEditor != null) { 15649 return true; 15650 } 15651 15652 return false; 15653 } 15654 canReplace()15655 boolean canReplace() { 15656 if (hasPasswordTransformationMethod()) { 15657 return false; 15658 } 15659 15660 return (mText.length() > 0) && (mText instanceof Editable) && (mEditor != null) 15661 && isSuggestionsEnabled() && mEditor.shouldOfferToShowSuggestions(); 15662 } 15663 15664 /** @hide */ 15665 @VisibleForTesting canShare()15666 public boolean canShare() { 15667 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned() 15668 || !getContext().getResources().getBoolean( 15669 com.android.internal.R.bool.config_textShareSupported)) { 15670 return false; 15671 } 15672 return canCopy(); 15673 } 15674 isDeviceProvisioned()15675 boolean isDeviceProvisioned() { 15676 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) { 15677 mDeviceProvisionedState = Settings.Global.getInt( 15678 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0 15679 ? DEVICE_PROVISIONED_YES 15680 : DEVICE_PROVISIONED_NO; 15681 } 15682 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; 15683 } 15684 15685 /** @hide */ 15686 @VisibleForTesting 15687 @UnsupportedAppUsage canPaste()15688 public boolean canPaste() { 15689 return (mText instanceof Editable 15690 && mEditor != null && mEditor.mKeyListener != null 15691 && getSelectionStart() >= 0 15692 && getSelectionEnd() >= 0 15693 && getClipboardManagerForUser().hasPrimaryClip()); 15694 } 15695 15696 /** @hide */ 15697 @VisibleForTesting canPasteAsPlainText()15698 public boolean canPasteAsPlainText() { 15699 if (!canPaste()) { 15700 return false; 15701 } 15702 15703 final ClipDescription description = 15704 getClipboardManagerForUser().getPrimaryClipDescription(); 15705 if (description == null) { 15706 return false; 15707 } 15708 final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 15709 return (isPlainType && description.isStyledText()) 15710 || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); 15711 } 15712 canProcessText()15713 boolean canProcessText() { 15714 if (getId() == View.NO_ID) { 15715 return false; 15716 } 15717 return canShare(); 15718 } 15719 15720 /** @hide */ 15721 @VisibleForTesting canSelectAllText()15722 public boolean canSelectAllText() { 15723 return canSelectText() && !hasPasswordTransformationMethod() 15724 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); 15725 } 15726 selectAllText()15727 boolean selectAllText() { 15728 if (mEditor != null) { 15729 // Hide the toolbar before changing the selection to avoid flickering. 15730 hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY); 15731 } 15732 final int length = mText.length(); 15733 Selection.setSelection(mSpannable, 0, length); 15734 return length > 0; 15735 } 15736 paste(boolean withFormatting)15737 private void paste(boolean withFormatting) { 15738 ClipboardManager clipboard = getClipboardManagerForUser(); 15739 ClipData clip = clipboard.getPrimaryClip(); 15740 if (clip == null) { 15741 return; 15742 } 15743 final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD) 15744 .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT) 15745 .build(); 15746 performReceiveContent(payload); 15747 sLastCutCopyOrTextChangedTime = 0; 15748 } 15749 shareSelectedText()15750 private void shareSelectedText() { 15751 String selectedText = getSelectedText(); 15752 if (selectedText != null && !selectedText.isEmpty()) { 15753 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); 15754 sharingIntent.setType("text/plain"); 15755 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); 15756 selectedText = TextUtils.trimToParcelableSize(selectedText); 15757 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); 15758 getContext().startActivity(Intent.createChooser(sharingIntent, null)); 15759 Selection.setSelection(mSpannable, getSelectionEnd()); 15760 } 15761 } 15762 15763 @CheckResult setPrimaryClip(ClipData clip)15764 private boolean setPrimaryClip(ClipData clip) { 15765 ClipboardManager clipboard = getClipboardManagerForUser(); 15766 try { 15767 clipboard.setPrimaryClip(clip); 15768 } catch (Throwable t) { 15769 return false; 15770 } 15771 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis(); 15772 return true; 15773 } 15774 15775 /** 15776 * Get the character offset closest to the specified absolute position. A typical use case is to 15777 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 15778 * 15779 * @param x The horizontal absolute position of a point on screen 15780 * @param y The vertical absolute position of a point on screen 15781 * @return the character offset for the character whose position is closest to the specified 15782 * position. Returns -1 if there is no layout. 15783 */ getOffsetForPosition(float x, float y)15784 public int getOffsetForPosition(float x, float y) { 15785 if (getLayout() == null) return -1; 15786 final int line = getLineAtCoordinate(y); 15787 final int offset = getOffsetAtCoordinate(line, x); 15788 return offset; 15789 } 15790 convertToLocalHorizontalCoordinate(float x)15791 float convertToLocalHorizontalCoordinate(float x) { 15792 x -= getTotalPaddingLeft(); 15793 // Clamp the position to inside of the view. 15794 x = Math.max(0.0f, x); 15795 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 15796 x += getScrollX(); 15797 return x; 15798 } 15799 15800 /** @hide */ 15801 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getLineAtCoordinate(float y)15802 public int getLineAtCoordinate(float y) { 15803 y -= getTotalPaddingTop(); 15804 // Clamp the position to inside of the view. 15805 y = Math.max(0.0f, y); 15806 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 15807 y += getScrollY(); 15808 return getLayout().getLineForVertical((int) y); 15809 } 15810 getLineAtCoordinateUnclamped(float y)15811 int getLineAtCoordinateUnclamped(float y) { 15812 y -= getTotalPaddingTop(); 15813 y += getScrollY(); 15814 return getLayout().getLineForVertical((int) y); 15815 } 15816 getOffsetAtCoordinate(int line, float x)15817 int getOffsetAtCoordinate(int line, float x) { 15818 x = convertToLocalHorizontalCoordinate(x); 15819 final int offset = getLayout().getOffsetForHorizontal(line, x); 15820 return transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CURSOR); 15821 } 15822 15823 /** 15824 * Convenient method to convert an offset on the transformed text to the original text. 15825 * @hide 15826 */ transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy)15827 public int transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy) { 15828 if (getTransformationMethod() == null) { 15829 return offset; 15830 } 15831 if (mTransformed instanceof OffsetMapping) { 15832 final OffsetMapping transformedText = (OffsetMapping) mTransformed; 15833 return transformedText.transformedToOriginal(offset, strategy); 15834 } 15835 return offset; 15836 } 15837 15838 /** 15839 * Convenient method to convert an offset on the original text to the transformed text. 15840 * @hide 15841 */ originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy)15842 public int originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy) { 15843 if (getTransformationMethod() == null) { 15844 return offset; 15845 } 15846 if (mTransformed instanceof OffsetMapping) { 15847 final OffsetMapping transformedText = (OffsetMapping) mTransformed; 15848 return transformedText.originalToTransformed(offset, strategy); 15849 } 15850 return offset; 15851 } 15852 /** 15853 * Handles drag events sent by the system following a call to 15854 * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int) 15855 * startDragAndDrop()}. 15856 * 15857 * <p>If this text view is not editable, delegates to the default {@link View#onDragEvent} 15858 * implementation. 15859 * 15860 * <p>If this text view is editable, accepts all drag actions (returns true for an 15861 * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event and all 15862 * subsequent drag events). While the drag is in progress, updates the cursor position 15863 * to follow the touch location. Once a drop event is received, handles content insertion 15864 * via {@link #performReceiveContent}. 15865 * 15866 * @param event The {@link android.view.DragEvent} sent by the system. 15867 * The {@link android.view.DragEvent#getAction()} method returns an action type constant 15868 * defined in DragEvent, indicating the type of drag event represented by this object. 15869 * @return Returns true if this text view is editable and delegates to super otherwise. 15870 * See {@link View#onDragEvent}. 15871 */ 15872 @Override onDragEvent(DragEvent event)15873 public boolean onDragEvent(DragEvent event) { 15874 if (mEditor == null || !mEditor.hasInsertionController()) { 15875 // If this TextView is not editable, defer to the default View implementation. This 15876 // will check for the presence of an OnReceiveContentListener and accept/reject 15877 // drag events depending on whether the listener is/isn't set. 15878 return super.onDragEvent(event); 15879 } 15880 switch (event.getAction()) { 15881 case DragEvent.ACTION_DRAG_STARTED: 15882 return true; 15883 15884 case DragEvent.ACTION_DRAG_ENTERED: 15885 TextView.this.requestFocus(); 15886 return true; 15887 15888 case DragEvent.ACTION_DRAG_LOCATION: 15889 if (mText instanceof Spannable) { 15890 final int offset = getOffsetForPosition(event.getX(), event.getY()); 15891 Selection.setSelection(mSpannable, offset); 15892 } 15893 return true; 15894 15895 case DragEvent.ACTION_DROP: 15896 if (mEditor != null) mEditor.onDrop(event); 15897 return true; 15898 15899 case DragEvent.ACTION_DRAG_ENDED: 15900 case DragEvent.ACTION_DRAG_EXITED: 15901 default: 15902 return true; 15903 } 15904 } 15905 isInBatchEditMode()15906 boolean isInBatchEditMode() { 15907 if (mEditor == null) return false; 15908 final Editor.InputMethodState ims = mEditor.mInputMethodState; 15909 if (ims != null) { 15910 return ims.mBatchEditNesting > 0; 15911 } 15912 return mEditor.mInBatchEditControllers; 15913 } 15914 15915 @Override onRtlPropertiesChanged(int layoutDirection)15916 public void onRtlPropertiesChanged(int layoutDirection) { 15917 super.onRtlPropertiesChanged(layoutDirection); 15918 15919 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic(); 15920 if (mTextDir != newTextDir) { 15921 mTextDir = newTextDir; 15922 if (mLayout != null) { 15923 checkForRelayout(); 15924 } 15925 } 15926 } 15927 15928 /** 15929 * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout. 15930 * The {@link TextDirectionHeuristic} that is used by TextView is only available after 15931 * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the 15932 * return value may not be the same as the one TextView uses if the View's layout direction is 15933 * not resolved or detached from parent root view. 15934 */ getTextDirectionHeuristic()15935 public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() { 15936 if (hasPasswordTransformationMethod()) { 15937 // passwords fields should be LTR 15938 return TextDirectionHeuristics.LTR; 15939 } 15940 15941 if (mEditor != null 15942 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 15943 == EditorInfo.TYPE_CLASS_PHONE) { 15944 // Phone numbers must be in the direction of the locale's digits. Most locales have LTR 15945 // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have 15946 // RTL digits. 15947 final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale()); 15948 final String zero = symbols.getDigitStrings()[0]; 15949 // In case the zero digit is multi-codepoint, just use the first codepoint to determine 15950 // direction. 15951 final int firstCodepoint = zero.codePointAt(0); 15952 final byte digitDirection = Character.getDirectionality(firstCodepoint); 15953 if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT 15954 || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) { 15955 return TextDirectionHeuristics.RTL; 15956 } else { 15957 return TextDirectionHeuristics.LTR; 15958 } 15959 } 15960 15961 // Always need to resolve layout direction first 15962 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 15963 15964 // Now, we can select the heuristic 15965 switch (getTextDirection()) { 15966 default: 15967 case TEXT_DIRECTION_FIRST_STRONG: 15968 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 15969 TextDirectionHeuristics.FIRSTSTRONG_LTR); 15970 case TEXT_DIRECTION_ANY_RTL: 15971 return TextDirectionHeuristics.ANYRTL_LTR; 15972 case TEXT_DIRECTION_LTR: 15973 return TextDirectionHeuristics.LTR; 15974 case TEXT_DIRECTION_RTL: 15975 return TextDirectionHeuristics.RTL; 15976 case TEXT_DIRECTION_LOCALE: 15977 return TextDirectionHeuristics.LOCALE; 15978 case TEXT_DIRECTION_FIRST_STRONG_LTR: 15979 return TextDirectionHeuristics.FIRSTSTRONG_LTR; 15980 case TEXT_DIRECTION_FIRST_STRONG_RTL: 15981 return TextDirectionHeuristics.FIRSTSTRONG_RTL; 15982 } 15983 } 15984 15985 /** 15986 * @hide 15987 */ 15988 @Override onResolveDrawables(int layoutDirection)15989 public void onResolveDrawables(int layoutDirection) { 15990 // No need to resolve twice 15991 if (mLastLayoutDirection == layoutDirection) { 15992 return; 15993 } 15994 mLastLayoutDirection = layoutDirection; 15995 15996 // Resolve drawables 15997 if (mDrawables != null) { 15998 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { 15999 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); 16000 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); 16001 applyCompoundDrawableTint(); 16002 } 16003 } 16004 } 16005 16006 /** 16007 * Prepares a drawable for display by propagating layout direction and 16008 * drawable state. 16009 * 16010 * @param dr the drawable to prepare 16011 */ prepareDrawableForDisplay(@ullable Drawable dr)16012 private void prepareDrawableForDisplay(@Nullable Drawable dr) { 16013 if (dr == null) { 16014 return; 16015 } 16016 16017 dr.setLayoutDirection(getLayoutDirection()); 16018 16019 if (dr.isStateful()) { 16020 dr.setState(getDrawableState()); 16021 dr.jumpToCurrentState(); 16022 } 16023 } 16024 16025 /** 16026 * @hide 16027 */ resetResolvedDrawables()16028 protected void resetResolvedDrawables() { 16029 super.resetResolvedDrawables(); 16030 mLastLayoutDirection = -1; 16031 } 16032 16033 /** 16034 * @hide 16035 */ viewClicked(InputMethodManager imm)16036 protected void viewClicked(InputMethodManager imm) { 16037 if (imm != null) { 16038 imm.viewClicked(this); 16039 } 16040 } 16041 16042 /** 16043 * Deletes the range of text [start, end[. 16044 * @hide 16045 */ 16046 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) deleteText_internal(int start, int end)16047 protected void deleteText_internal(int start, int end) { 16048 ((Editable) mText).delete(start, end); 16049 } 16050 16051 /** 16052 * Replaces the range of text [start, end[ by replacement text 16053 * @hide 16054 */ replaceText_internal(int start, int end, CharSequence text)16055 protected void replaceText_internal(int start, int end, CharSequence text) { 16056 ((Editable) mText).replace(start, end, text); 16057 } 16058 16059 /** 16060 * Sets a span on the specified range of text 16061 * @hide 16062 */ setSpan_internal(Object span, int start, int end, int flags)16063 protected void setSpan_internal(Object span, int start, int end, int flags) { 16064 ((Editable) mText).setSpan(span, start, end, flags); 16065 } 16066 16067 /** 16068 * Moves the cursor to the specified offset position in text 16069 * @hide 16070 */ setCursorPosition_internal(int start, int end)16071 protected void setCursorPosition_internal(int start, int end) { 16072 Selection.setSelection(((Editable) mText), start, end); 16073 } 16074 16075 /** 16076 * An Editor should be created as soon as any of the editable-specific fields (grouped 16077 * inside the Editor object) is assigned to a non-default value. 16078 * This method will create the Editor if needed. 16079 * 16080 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 16081 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 16082 * Editor for backward compatibility, as soon as one of these fields is assigned. 16083 * 16084 * Also note that for performance reasons, the mEditor is created when needed, but not 16085 * reset when no more edit-specific fields are needed. 16086 */ 16087 @UnsupportedAppUsage createEditorIfNeeded()16088 private void createEditorIfNeeded() { 16089 if (mEditor == null) { 16090 mEditor = new Editor(this); 16091 } 16092 } 16093 16094 /** 16095 * @hide 16096 */ 16097 @Override 16098 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getIterableTextForAccessibility()16099 public CharSequence getIterableTextForAccessibility() { 16100 return mText; 16101 } 16102 ensureIterableTextForAccessibilitySelectable()16103 private void ensureIterableTextForAccessibilitySelectable() { 16104 if (!(mText instanceof Spannable)) { 16105 setText(mText, BufferType.SPANNABLE); 16106 if (getLayout() == null) { 16107 assumeLayout(); 16108 } 16109 } 16110 } 16111 16112 /** 16113 * @hide 16114 */ 16115 @Override getIteratorForGranularity(int granularity)16116 public TextSegmentIterator getIteratorForGranularity(int granularity) { 16117 switch (granularity) { 16118 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 16119 Spannable text = (Spannable) getIterableTextForAccessibility(); 16120 if (!TextUtils.isEmpty(text) && getLayout() != null) { 16121 AccessibilityIterators.LineTextSegmentIterator iterator = 16122 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 16123 iterator.initialize(text, getLayout()); 16124 return iterator; 16125 } 16126 } break; 16127 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 16128 Spannable text = (Spannable) getIterableTextForAccessibility(); 16129 if (!TextUtils.isEmpty(text) && getLayout() != null) { 16130 AccessibilityIterators.PageTextSegmentIterator iterator = 16131 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 16132 iterator.initialize(this); 16133 return iterator; 16134 } 16135 } break; 16136 } 16137 return super.getIteratorForGranularity(granularity); 16138 } 16139 16140 /** 16141 * @hide 16142 */ 16143 @Override getAccessibilitySelectionStart()16144 public int getAccessibilitySelectionStart() { 16145 return getSelectionStart(); 16146 } 16147 16148 /** 16149 * @hide 16150 */ isAccessibilitySelectionExtendable()16151 public boolean isAccessibilitySelectionExtendable() { 16152 return true; 16153 } 16154 16155 /** 16156 * @hide 16157 */ prepareForExtendedAccessibilitySelection()16158 public void prepareForExtendedAccessibilitySelection() { 16159 requestFocusOnNonEditableSelectableText(); 16160 } 16161 16162 /** 16163 * @hide 16164 */ 16165 @Override getAccessibilitySelectionEnd()16166 public int getAccessibilitySelectionEnd() { 16167 return getSelectionEnd(); 16168 } 16169 16170 /** 16171 * @hide 16172 */ 16173 @Override setAccessibilitySelection(int start, int end)16174 public void setAccessibilitySelection(int start, int end) { 16175 if (getAccessibilitySelectionStart() == start 16176 && getAccessibilitySelectionEnd() == end) { 16177 return; 16178 } 16179 CharSequence text = getIterableTextForAccessibility(); 16180 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 16181 Selection.setSelection((Spannable) text, start, end); 16182 } else { 16183 Selection.removeSelection((Spannable) text); 16184 } 16185 // Hide all selection controllers used for adjusting selection 16186 // since we are doing so explicitlty by other means and these 16187 // controllers interact with how selection behaves. 16188 if (mEditor != null) { 16189 mEditor.hideCursorAndSpanControllers(); 16190 mEditor.stopTextActionMode(); 16191 } 16192 } 16193 16194 /** @hide */ 16195 @Override encodeProperties(@onNull ViewHierarchyEncoder stream)16196 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 16197 super.encodeProperties(stream); 16198 16199 TruncateAt ellipsize = getEllipsize(); 16200 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); 16201 stream.addProperty("text:textSize", getTextSize()); 16202 stream.addProperty("text:scaledTextSize", getScaledTextSize()); 16203 stream.addProperty("text:typefaceStyle", getTypefaceStyle()); 16204 stream.addProperty("text:selectionStart", getSelectionStart()); 16205 stream.addProperty("text:selectionEnd", getSelectionEnd()); 16206 stream.addProperty("text:curTextColor", mCurTextColor); 16207 stream.addUserProperty("text:text", mText == null ? null : mText.toString()); 16208 stream.addProperty("text:gravity", mGravity); 16209 } 16210 16211 /** 16212 * User interface state that is stored by TextView for implementing 16213 * {@link View#onSaveInstanceState}. 16214 */ 16215 public static class SavedState extends BaseSavedState { 16216 int selStart = -1; 16217 int selEnd = -1; 16218 @UnsupportedAppUsage 16219 CharSequence text; 16220 boolean frozenWithFocus; 16221 CharSequence error; 16222 ParcelableParcel editorState; // Optional state from Editor. 16223 SavedState(Parcelable superState)16224 SavedState(Parcelable superState) { 16225 super(superState); 16226 } 16227 16228 @Override writeToParcel(Parcel out, int flags)16229 public void writeToParcel(Parcel out, int flags) { 16230 super.writeToParcel(out, flags); 16231 out.writeInt(selStart); 16232 out.writeInt(selEnd); 16233 out.writeInt(frozenWithFocus ? 1 : 0); 16234 TextUtils.writeToParcel(text, out, flags); 16235 16236 if (error == null) { 16237 out.writeInt(0); 16238 } else { 16239 out.writeInt(1); 16240 TextUtils.writeToParcel(error, out, flags); 16241 } 16242 16243 if (editorState == null) { 16244 out.writeInt(0); 16245 } else { 16246 out.writeInt(1); 16247 editorState.writeToParcel(out, flags); 16248 } 16249 } 16250 16251 @Override toString()16252 public String toString() { 16253 String str = "TextView.SavedState{" 16254 + Integer.toHexString(System.identityHashCode(this)) 16255 + " start=" + selStart + " end=" + selEnd; 16256 if (text != null) { 16257 str += " text=" + text; 16258 } 16259 return str + "}"; 16260 } 16261 16262 @SuppressWarnings("hiding") 16263 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR = 16264 new Parcelable.Creator<SavedState>() { 16265 public SavedState createFromParcel(Parcel in) { 16266 return new SavedState(in); 16267 } 16268 16269 public SavedState[] newArray(int size) { 16270 return new SavedState[size]; 16271 } 16272 }; 16273 SavedState(Parcel in)16274 private SavedState(Parcel in) { 16275 super(in); 16276 selStart = in.readInt(); 16277 selEnd = in.readInt(); 16278 frozenWithFocus = (in.readInt() != 0); 16279 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 16280 16281 if (in.readInt() != 0) { 16282 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 16283 } 16284 16285 if (in.readInt() != 0) { 16286 editorState = ParcelableParcel.CREATOR.createFromParcel(in); 16287 } 16288 } 16289 } 16290 16291 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 16292 @NonNull 16293 private char[] mChars; 16294 private int mStart, mLength; 16295 CharWrapper(@onNull char[] chars, int start, int len)16296 CharWrapper(@NonNull char[] chars, int start, int len) { 16297 mChars = chars; 16298 mStart = start; 16299 mLength = len; 16300 } 16301 set(@onNull char[] chars, int start, int len)16302 /* package */ void set(@NonNull char[] chars, int start, int len) { 16303 mChars = chars; 16304 mStart = start; 16305 mLength = len; 16306 } 16307 length()16308 public int length() { 16309 return mLength; 16310 } 16311 charAt(int off)16312 public char charAt(int off) { 16313 return mChars[off + mStart]; 16314 } 16315 16316 @Override toString()16317 public String toString() { 16318 return new String(mChars, mStart, mLength); 16319 } 16320 subSequence(int start, int end)16321 public CharSequence subSequence(int start, int end) { 16322 if (start < 0 || end < 0 || start > mLength || end > mLength) { 16323 throw new IndexOutOfBoundsException(start + ", " + end); 16324 } 16325 16326 return new String(mChars, start + mStart, end - start); 16327 } 16328 getChars(int start, int end, char[] buf, int off)16329 public void getChars(int start, int end, char[] buf, int off) { 16330 if (start < 0 || end < 0 || start > mLength || end > mLength) { 16331 throw new IndexOutOfBoundsException(start + ", " + end); 16332 } 16333 16334 System.arraycopy(mChars, start + mStart, buf, off, end - start); 16335 } 16336 16337 @Override drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)16338 public void drawText(BaseCanvas c, int start, int end, 16339 float x, float y, Paint p) { 16340 c.drawText(mChars, start + mStart, end - start, x, y, p); 16341 } 16342 16343 @Override drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)16344 public void drawTextRun(BaseCanvas c, int start, int end, 16345 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 16346 int count = end - start; 16347 int contextCount = contextEnd - contextStart; 16348 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 16349 contextCount, x, y, isRtl, p); 16350 } 16351 measureText(int start, int end, Paint p)16352 public float measureText(int start, int end, Paint p) { 16353 return p.measureText(mChars, start + mStart, end - start); 16354 } 16355 getTextWidths(int start, int end, float[] widths, Paint p)16356 public int getTextWidths(int start, int end, float[] widths, Paint p) { 16357 return p.getTextWidths(mChars, start + mStart, end - start, widths); 16358 } 16359 getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)16360 public float getTextRunAdvances(int start, int end, int contextStart, 16361 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 16362 Paint p) { 16363 int count = end - start; 16364 int contextCount = contextEnd - contextStart; 16365 return p.getTextRunAdvances(mChars, start + mStart, count, 16366 contextStart + mStart, contextCount, isRtl, advances, 16367 advancesIndex); 16368 } 16369 getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)16370 public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, 16371 int offset, int cursorOpt, Paint p) { 16372 int contextCount = contextEnd - contextStart; 16373 return p.getTextRunCursor(mChars, contextStart + mStart, 16374 contextCount, isRtl, offset + mStart, cursorOpt); 16375 } 16376 } 16377 16378 private static final class Marquee { 16379 // TODO: Add an option to configure this 16380 private static final float MARQUEE_DELTA_MAX = 0.07f; 16381 private static final int MARQUEE_DELAY = 1200; 16382 private static final int MARQUEE_DP_PER_SECOND = 30; 16383 16384 private static final byte MARQUEE_STOPPED = 0x0; 16385 private static final byte MARQUEE_STARTING = 0x1; 16386 private static final byte MARQUEE_RUNNING = 0x2; 16387 16388 private final WeakReference<TextView> mView; 16389 private final Choreographer mChoreographer; 16390 16391 private byte mStatus = MARQUEE_STOPPED; 16392 private final float mPixelsPerMs; 16393 private float mMaxScroll; 16394 private float mMaxFadeScroll; 16395 private float mGhostStart; 16396 private float mGhostOffset; 16397 private float mFadeStop; 16398 private int mRepeatLimit; 16399 16400 private float mScroll; 16401 private long mLastAnimationMs; 16402 Marquee(TextView v)16403 Marquee(TextView v) { 16404 final float density = v.getContext().getResources().getDisplayMetrics().density; 16405 mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f; 16406 mView = new WeakReference<TextView>(v); 16407 mChoreographer = Choreographer.getInstance(); 16408 } 16409 16410 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 16411 @Override 16412 public void doFrame(long frameTimeNanos) { 16413 tick(); 16414 } 16415 }; 16416 16417 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 16418 @Override 16419 public void doFrame(long frameTimeNanos) { 16420 mStatus = MARQUEE_RUNNING; 16421 mLastAnimationMs = mChoreographer.getFrameTime(); 16422 tick(); 16423 } 16424 }; 16425 16426 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 16427 @Override 16428 public void doFrame(long frameTimeNanos) { 16429 if (mStatus == MARQUEE_RUNNING) { 16430 if (mRepeatLimit >= 0) { 16431 mRepeatLimit--; 16432 } 16433 start(mRepeatLimit); 16434 } 16435 } 16436 }; 16437 tick()16438 void tick() { 16439 if (mStatus != MARQUEE_RUNNING) { 16440 return; 16441 } 16442 16443 mChoreographer.removeFrameCallback(mTickCallback); 16444 16445 final TextView textView = mView.get(); 16446 if (textView != null && textView.isAggregatedVisible() 16447 && (textView.isFocused() || textView.isSelected())) { 16448 long currentMs = mChoreographer.getFrameTime(); 16449 long deltaMs = currentMs - mLastAnimationMs; 16450 mLastAnimationMs = currentMs; 16451 float deltaPx = deltaMs * mPixelsPerMs; 16452 mScroll += deltaPx; 16453 if (mScroll > mMaxScroll) { 16454 mScroll = mMaxScroll; 16455 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 16456 } else { 16457 mChoreographer.postFrameCallback(mTickCallback); 16458 } 16459 textView.invalidate(); 16460 } 16461 } 16462 stop()16463 void stop() { 16464 mStatus = MARQUEE_STOPPED; 16465 mChoreographer.removeFrameCallback(mStartCallback); 16466 mChoreographer.removeFrameCallback(mRestartCallback); 16467 mChoreographer.removeFrameCallback(mTickCallback); 16468 resetScroll(); 16469 } 16470 resetScroll()16471 private void resetScroll() { 16472 mScroll = 0.0f; 16473 final TextView textView = mView.get(); 16474 if (textView != null) textView.invalidate(); 16475 } 16476 start(int repeatLimit)16477 void start(int repeatLimit) { 16478 if (repeatLimit == 0) { 16479 stop(); 16480 return; 16481 } 16482 mRepeatLimit = repeatLimit; 16483 final TextView textView = mView.get(); 16484 if (textView != null && textView.mLayout != null) { 16485 mStatus = MARQUEE_STARTING; 16486 mScroll = 0.0f; 16487 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() 16488 - textView.getCompoundPaddingRight(); 16489 final float lineWidth = textView.mLayout.getLineWidth(0); 16490 final float gap = textWidth / 3.0f; 16491 mGhostStart = lineWidth - textWidth + gap; 16492 mMaxScroll = mGhostStart + textWidth; 16493 mGhostOffset = lineWidth + gap; 16494 mFadeStop = lineWidth + textWidth / 6.0f; 16495 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 16496 16497 textView.invalidate(); 16498 mChoreographer.postFrameCallback(mStartCallback); 16499 } 16500 } 16501 getGhostOffset()16502 float getGhostOffset() { 16503 return mGhostOffset; 16504 } 16505 getScroll()16506 float getScroll() { 16507 return mScroll; 16508 } 16509 getMaxFadeScroll()16510 float getMaxFadeScroll() { 16511 return mMaxFadeScroll; 16512 } 16513 shouldDrawLeftFade()16514 boolean shouldDrawLeftFade() { 16515 return mScroll <= mFadeStop; 16516 } 16517 shouldDrawGhost()16518 boolean shouldDrawGhost() { 16519 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 16520 } 16521 isRunning()16522 boolean isRunning() { 16523 return mStatus == MARQUEE_RUNNING; 16524 } 16525 isStopped()16526 boolean isStopped() { 16527 return mStatus == MARQUEE_STOPPED; 16528 } 16529 } 16530 16531 private class ChangeWatcher implements TextWatcher, SpanWatcher { 16532 16533 private CharSequence mBeforeText; 16534 beforeTextChanged(CharSequence buffer, int start, int before, int after)16535 public void beforeTextChanged(CharSequence buffer, int start, 16536 int before, int after) { 16537 if (DEBUG_EXTRACT) { 16538 Log.v(LOG_TAG, "beforeTextChanged start=" + start 16539 + " before=" + before + " after=" + after + ": " + buffer); 16540 } 16541 16542 if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) { 16543 mBeforeText = mTransformed.toString(); 16544 } 16545 16546 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 16547 } 16548 onTextChanged(CharSequence buffer, int start, int before, int after)16549 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 16550 if (DEBUG_EXTRACT) { 16551 Log.v(LOG_TAG, "onTextChanged start=" + start 16552 + " before=" + before + " after=" + after + ": " + buffer); 16553 } 16554 TextView.this.handleTextChanged(buffer, start, before, after); 16555 16556 if (isVisibleToAccessibility()) { 16557 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 16558 mBeforeText = null; 16559 } 16560 } 16561 afterTextChanged(Editable buffer)16562 public void afterTextChanged(Editable buffer) { 16563 if (DEBUG_EXTRACT) { 16564 Log.v(LOG_TAG, "afterTextChanged: " + buffer); 16565 } 16566 TextView.this.sendAfterTextChanged(buffer); 16567 16568 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 16569 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 16570 } 16571 } 16572 onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)16573 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 16574 if (DEBUG_EXTRACT) { 16575 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 16576 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 16577 } 16578 TextView.this.spanChange(buf, what, s, st, e, en); 16579 } 16580 onSpanAdded(Spannable buf, Object what, int s, int e)16581 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 16582 if (DEBUG_EXTRACT) { 16583 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf); 16584 } 16585 TextView.this.spanChange(buf, what, -1, s, -1, e); 16586 } 16587 onSpanRemoved(Spannable buf, Object what, int s, int e)16588 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 16589 if (DEBUG_EXTRACT) { 16590 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf); 16591 } 16592 TextView.this.spanChange(buf, what, s, -1, e, -1); 16593 } 16594 } 16595 16596 /** @hide */ 16597 @Override onInputConnectionOpenedInternal(@onNull InputConnection ic, @NonNull EditorInfo editorInfo, @Nullable Handler handler)16598 public void onInputConnectionOpenedInternal(@NonNull InputConnection ic, 16599 @NonNull EditorInfo editorInfo, @Nullable Handler handler) { 16600 if (mEditor != null) { 16601 mEditor.getDefaultOnReceiveContentListener().setInputConnectionInfo(this, ic, 16602 editorInfo); 16603 } 16604 } 16605 16606 /** @hide */ 16607 @Override onInputConnectionClosedInternal()16608 public void onInputConnectionClosedInternal() { 16609 if (mEditor != null) { 16610 mEditor.getDefaultOnReceiveContentListener().clearInputConnectionInfo(); 16611 } 16612 } 16613 16614 /** 16615 * Default {@link TextView} implementation for receiving content. Apps wishing to provide 16616 * custom behavior should configure a listener via {@link #setOnReceiveContentListener}. 16617 * 16618 * <p>For non-editable TextViews the default behavior is a no-op (returns the passed-in 16619 * content without acting on it). 16620 * 16621 * <p>For editable TextViews the default behavior is to insert text into the view, coercing 16622 * non-text content to text as needed. The MIME types "text/plain" and "text/html" have 16623 * well-defined behavior for this, while other MIME types have reasonable fallback behavior 16624 * (see {@link ClipData.Item#coerceToStyledText}). 16625 * 16626 * @param payload The content to insert and related metadata. 16627 * 16628 * @return The portion of the passed-in content that was not handled (may be all, some, or none 16629 * of the passed-in content). 16630 */ 16631 @Nullable 16632 @Override onReceiveContent(@onNull ContentInfo payload)16633 public ContentInfo onReceiveContent(@NonNull ContentInfo payload) { 16634 if (mEditor != null) { 16635 return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload); 16636 } 16637 return payload; 16638 } 16639 logCursor(String location, @Nullable String msgFormat, Object ... msgArgs)16640 private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { 16641 if (msgFormat == null) { 16642 Log.d(LOG_TAG, location); 16643 } else { 16644 Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs)); 16645 } 16646 } 16647 16648 /** 16649 * Collects a {@link ViewTranslationRequest} which represents the content to be translated in 16650 * the view. 16651 * 16652 * <p>NOTE: When overriding the method, it should not collect a request to translate this 16653 * TextView if it is displaying a password. 16654 * 16655 * @param supportedFormats the supported translation format. The value could be {@link 16656 * android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}. 16657 * @param requestsCollector {@link Consumer} to receiver the {@link ViewTranslationRequest} 16658 * which contains the information to be translated. 16659 */ 16660 @Override onCreateViewTranslationRequest(@onNull int[] supportedFormats, @NonNull Consumer<ViewTranslationRequest> requestsCollector)16661 public void onCreateViewTranslationRequest(@NonNull int[] supportedFormats, 16662 @NonNull Consumer<ViewTranslationRequest> requestsCollector) { 16663 if (supportedFormats == null || supportedFormats.length == 0) { 16664 if (UiTranslationController.DEBUG) { 16665 Log.w(LOG_TAG, "Do not provide the support translation formats."); 16666 } 16667 return; 16668 } 16669 ViewTranslationRequest.Builder requestBuilder = 16670 new ViewTranslationRequest.Builder(getAutofillId()); 16671 // Support Text translation 16672 if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) { 16673 if (mText == null || mText.length() == 0) { 16674 if (UiTranslationController.DEBUG) { 16675 Log.w(LOG_TAG, "Cannot create translation request for the empty text."); 16676 } 16677 return; 16678 } 16679 boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod(); 16680 if (isTextEditable() || isPassword) { 16681 Log.w(LOG_TAG, "Cannot create translation request. editable = " 16682 + isTextEditable() + ", isPassword = " + isPassword); 16683 return; 16684 } 16685 // TODO(b/176488462): apply the view's important for translation 16686 requestBuilder.setValue(ViewTranslationRequest.ID_TEXT, 16687 TranslationRequestValue.forText(mText)); 16688 if (!TextUtils.isEmpty(getContentDescription())) { 16689 requestBuilder.setValue(ViewTranslationRequest.ID_CONTENT_DESCRIPTION, 16690 TranslationRequestValue.forText(getContentDescription())); 16691 } 16692 } 16693 requestsCollector.accept(requestBuilder.build()); 16694 } 16695 } 16696