1 /* 2 * Copyright (C) 2015 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 androidx.appcompat.widget; 18 19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; 20 import static androidx.appcompat.widget.ViewUtils.SDK_LEVEL_SUPPORTS_AUTOSIZE; 21 22 import android.annotation.SuppressLint; 23 import android.content.Context; 24 import android.content.res.ColorStateList; 25 import android.graphics.PorterDuff; 26 import android.graphics.Typeface; 27 import android.graphics.drawable.Drawable; 28 import android.os.Build; 29 import android.os.Build.VERSION_CODES; 30 import android.text.InputFilter; 31 import android.util.AttributeSet; 32 import android.util.Log; 33 import android.view.ActionMode; 34 import android.view.inputmethod.EditorInfo; 35 import android.view.inputmethod.InputConnection; 36 import android.view.inputmethod.InputMethodManager; 37 import android.view.textclassifier.TextClassifier; 38 import android.widget.TextView; 39 40 import androidx.annotation.DrawableRes; 41 import androidx.annotation.FloatRange; 42 import androidx.annotation.IntRange; 43 import androidx.annotation.Px; 44 import androidx.annotation.RequiresApi; 45 import androidx.annotation.RestrictTo; 46 import androidx.annotation.UiThread; 47 import androidx.appcompat.content.res.AppCompatResources; 48 import androidx.core.graphics.TypefaceCompat; 49 import androidx.core.text.PrecomputedTextCompat; 50 import androidx.core.view.TintableBackgroundView; 51 import androidx.core.widget.AutoSizeableTextView; 52 import androidx.core.widget.TextViewCompat; 53 import androidx.core.widget.TintableCompoundDrawablesView; 54 import androidx.resourceinspection.annotation.AppCompatShadowedAttributes; 55 56 import org.jspecify.annotations.NonNull; 57 import org.jspecify.annotations.Nullable; 58 59 import java.util.concurrent.ExecutionException; 60 import java.util.concurrent.Future; 61 62 /** 63 * A {@link TextView} which supports compatible features on older versions of the platform, 64 * including: 65 * <ul> 66 * <li>Allows dynamic tint of its background via the background tint methods in 67 * {@link androidx.core.view.ViewCompat}.</li> 68 * <li>Allows setting of the background tint using 69 * {@link androidx.appcompat.R.attr#backgroundTint} and 70 * {@link androidx.appcompat.R.attr#backgroundTintMode}.</li> 71 * <li>Supports auto-sizing via {@link androidx.core.widget.TextViewCompat} by allowing to instruct 72 * a {@link TextView} to let the size of the text expand or contract automatically to fill its 73 * layout based on the TextView's characteristics and boundaries. The style attributes associated 74 * with auto-sizing are 75 * {@link androidx.appcompat.R.attr#autoSizeTextType}, 76 * {@link androidx.appcompat.R.attr#autoSizeMinTextSize}, 77 * {@link androidx.appcompat.R.attr#autoSizeMaxTextSize}, 78 * {@link androidx.appcompat.R.attr#autoSizeStepGranularity} and 79 * {@link androidx.appcompat.R.attr#autoSizePresetSizes}, all of which work back to 80 * {@link VERSION_CODES#ICE_CREAM_SANDWICH Ice Cream Sandwich}.</li> 81 * </ul> 82 * 83 * <p>This will automatically be used when you use {@link TextView} in your layouts 84 * and the top-level activity / dialog is provided by 85 * <a href="{@docRoot}topic/libraries/support-library/packages.html#v7-appcompat">appcompat</a>. 86 * You should only need to manually use this class when writing custom views.</p> 87 */ 88 @AppCompatShadowedAttributes 89 public class AppCompatTextView extends TextView implements TintableBackgroundView, 90 TintableCompoundDrawablesView, AutoSizeableTextView, EmojiCompatConfigurationView { 91 92 private static final String TAG = "AppCompatTextView"; 93 private final AppCompatBackgroundHelper mBackgroundTintHelper; 94 private final AppCompatTextHelper mTextHelper; 95 private final AppCompatTextClassifierHelper mTextClassifierHelper; 96 @SuppressWarnings("NotNullFieldNotInitialized") // initialized in getter 97 private @NonNull AppCompatEmojiTextHelper mEmojiTextViewHelper; 98 99 private boolean mIsSetTypefaceProcessing = false; 100 101 /** 102 * Equivalent to Typeface.mOriginalTypeface. 103 * Used to correctly emulate the behavior of getTypeface(), because we need to call setTypeface 104 * directly in order to implement caching of variation instances of typefaces. 105 */ 106 private Typeface mOriginalTypeface; 107 108 /** 109 * The last Typeface we are aware of being set on {@link #getPaint()}. 110 * Used to detect if it has been changed out from under us via directly calling 111 * {@link android.graphics.Paint#setTypeface(Typeface)} or 112 * {@link android.graphics.Paint#setFontVariationSettings(String)} 113 * (which is not supported, so this is a best-effort workaround). 114 * 115 * @see #setTypefaceInternal(Typeface) 116 */ 117 private Typeface mLastKnownTypefaceSetOnPaint; 118 119 /** 120 * The currently applied font variation settings. 121 * Used to make getFontVariationSettings somewhat more accurate with Typeface instance caching, 122 * as we don't call super.setFontVariationSettings. 123 */ 124 private String mFontVariationSettings; 125 126 private @Nullable SuperCaller mSuperCaller = null; 127 128 private @Nullable Future<PrecomputedTextCompat> mPrecomputedTextFuture; 129 AppCompatTextView(@onNull Context context)130 public AppCompatTextView(@NonNull Context context) { 131 this(context, null); 132 } 133 AppCompatTextView(@onNull Context context, @Nullable AttributeSet attrs)134 public AppCompatTextView(@NonNull Context context, @Nullable AttributeSet attrs) { 135 this(context, attrs, android.R.attr.textViewStyle); 136 } 137 AppCompatTextView( @onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)138 public AppCompatTextView( 139 @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 140 super(TintContextWrapper.wrap(context), attrs, defStyleAttr); 141 142 ThemeUtils.checkAppCompatTheme(this, getContext()); 143 144 mBackgroundTintHelper = new AppCompatBackgroundHelper(this); 145 mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr); 146 147 mTextHelper = new AppCompatTextHelper(this); 148 mTextHelper.loadFromAttributes(attrs, defStyleAttr); 149 mTextHelper.applyCompoundDrawablesTints(); 150 151 mTextClassifierHelper = new AppCompatTextClassifierHelper(this); 152 153 AppCompatEmojiTextHelper emojiTextViewHelper = getEmojiTextViewHelper(); 154 emojiTextViewHelper.loadFromAttributes(attrs, defStyleAttr); 155 } 156 157 /** 158 * This may be called from super constructors. 159 */ getEmojiTextViewHelper()160 private @NonNull AppCompatEmojiTextHelper getEmojiTextViewHelper() { 161 //noinspection ConstantConditions 162 if (mEmojiTextViewHelper == null) { 163 mEmojiTextViewHelper = new AppCompatEmojiTextHelper(this); 164 } 165 return mEmojiTextViewHelper; 166 } 167 168 @Override setBackgroundResource(@rawableRes int resId)169 public void setBackgroundResource(@DrawableRes int resId) { 170 super.setBackgroundResource(resId); 171 if (mBackgroundTintHelper != null) { 172 mBackgroundTintHelper.onSetBackgroundResource(resId); 173 } 174 } 175 176 @Override setBackgroundDrawable(@ullable Drawable background)177 public void setBackgroundDrawable(@Nullable Drawable background) { 178 super.setBackgroundDrawable(background); 179 if (mBackgroundTintHelper != null) { 180 mBackgroundTintHelper.onSetBackgroundDrawable(background); 181 } 182 } 183 184 /** 185 * This should be accessed via 186 * {@link androidx.core.view.ViewCompat#setBackgroundTintList(android.view.View, ColorStateList)} 187 */ 188 @RestrictTo(LIBRARY_GROUP_PREFIX) 189 @Override setSupportBackgroundTintList(@ullable ColorStateList tint)190 public void setSupportBackgroundTintList(@Nullable ColorStateList tint) { 191 if (mBackgroundTintHelper != null) { 192 mBackgroundTintHelper.setSupportBackgroundTintList(tint); 193 } 194 } 195 196 /** 197 * This should be accessed via 198 * {@link androidx.core.view.ViewCompat#getBackgroundTintList(android.view.View)} 199 */ 200 @RestrictTo(LIBRARY_GROUP_PREFIX) 201 @Override getSupportBackgroundTintList()202 public @Nullable ColorStateList getSupportBackgroundTintList() { 203 return mBackgroundTintHelper != null 204 ? mBackgroundTintHelper.getSupportBackgroundTintList() : null; 205 } 206 207 /** 208 * This should be accessed via 209 * {@link androidx.core.view.ViewCompat#setBackgroundTintMode(android.view.View, PorterDuff.Mode)} 210 */ 211 @RestrictTo(LIBRARY_GROUP_PREFIX) 212 @Override setSupportBackgroundTintMode(PorterDuff.@ullable Mode tintMode)213 public void setSupportBackgroundTintMode(PorterDuff.@Nullable Mode tintMode) { 214 if (mBackgroundTintHelper != null) { 215 mBackgroundTintHelper.setSupportBackgroundTintMode(tintMode); 216 } 217 } 218 219 /** 220 * This should be accessed via 221 * {@link androidx.core.view.ViewCompat#getBackgroundTintMode(android.view.View)} 222 */ 223 @RestrictTo(LIBRARY_GROUP_PREFIX) 224 @Override getSupportBackgroundTintMode()225 public PorterDuff.@Nullable Mode getSupportBackgroundTintMode() { 226 return mBackgroundTintHelper != null 227 ? mBackgroundTintHelper.getSupportBackgroundTintMode() : null; 228 } 229 230 @Override setTextAppearance(Context context, int resId)231 public void setTextAppearance(Context context, int resId) { 232 super.setTextAppearance(context, resId); 233 if (mTextHelper != null) { 234 mTextHelper.onSetTextAppearance(context, resId); 235 } 236 } 237 238 /** 239 * Set font variation settings. 240 * See {@link TextView#setFontVariationSettings(String)} for details. 241 * <p> 242 * <em>Note:</em> Due to performance optimizations, 243 * {@code getPaint().getFontVariationSettings()} will be less reliable than if not using 244 * AppCompatTextView. You should prefer {@link #getFontVariationSettings()}, which will be more 245 * accurate. However, neither approach will work correctly if using Typeface objects with 246 * embedded font variation settings. 247 */ 248 // Reference comparison with mLastKnownTypefaceSetOnPaint is intended; 249 // it should in fact be the exact instance, because we set it. 250 @SuppressWarnings("ReferenceEquality") 251 @RequiresApi(26) 252 @Override setFontVariationSettings(@ullable String fontVariationSettings)253 public boolean setFontVariationSettings(@Nullable String fontVariationSettings) { 254 Typeface baseTypeface = mOriginalTypeface; 255 // Try to work around apps mutating the result of getPaint() 256 // See setTypefaceInternal doc comment for details. 257 if (mLastKnownTypefaceSetOnPaint != getPaint().getTypeface()) { 258 Log.w(TAG, "getPaint().getTypeface() changed unexpectedly." 259 + " App code should not modify the result of getPaint()."); 260 // Best effort: use that new Typeface instead. 261 baseTypeface = getPaint().getTypeface(); 262 } 263 Typeface variationTypefaceInstance = AppCompatTextHelper.Api26Impl.createVariationInstance( 264 baseTypeface, fontVariationSettings); 265 if (variationTypefaceInstance != null) { 266 setTypefaceInternal(variationTypefaceInstance); 267 mFontVariationSettings = fontVariationSettings; 268 return true; 269 } else { 270 return false; 271 } 272 } 273 274 @RequiresApi(26) 275 @Override getFontVariationSettings()276 public @Nullable String getFontVariationSettings() { 277 return mFontVariationSettings; 278 } 279 280 @Override setFilters(@uppressWarnings"ArrayReturn") InputFilter @onNull [] filters)281 public void setFilters(@SuppressWarnings("ArrayReturn") InputFilter @NonNull [] filters) { 282 super.setFilters(getEmojiTextViewHelper().getFilters(filters)); 283 } 284 285 @Override setAllCaps(boolean allCaps)286 public void setAllCaps(boolean allCaps) { 287 super.setAllCaps(allCaps); 288 getEmojiTextViewHelper().setAllCaps(allCaps); 289 } 290 291 @Override setEmojiCompatEnabled(boolean enabled)292 public void setEmojiCompatEnabled(boolean enabled) { 293 getEmojiTextViewHelper().setEnabled(enabled); 294 } 295 296 @Override isEmojiCompatEnabled()297 public boolean isEmojiCompatEnabled() { 298 return getEmojiTextViewHelper().isEnabled(); 299 } 300 301 @Override drawableStateChanged()302 protected void drawableStateChanged() { 303 super.drawableStateChanged(); 304 if (mBackgroundTintHelper != null) { 305 mBackgroundTintHelper.applySupportBackgroundTint(); 306 } 307 if (mTextHelper != null) { 308 mTextHelper.applyCompoundDrawablesTints(); 309 } 310 } 311 312 @Override onLayout(boolean changed, int left, int top, int right, int bottom)313 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 314 super.onLayout(changed, left, top, right, bottom); 315 if (mTextHelper != null) { 316 mTextHelper.onLayout(changed, left, top, right, bottom); 317 } 318 } 319 320 @Override setTextSize(int unit, float size)321 public void setTextSize(int unit, float size) { 322 if (SDK_LEVEL_SUPPORTS_AUTOSIZE) { 323 super.setTextSize(unit, size); 324 } else { 325 if (mTextHelper != null) { 326 mTextHelper.setTextSize(unit, size); 327 } 328 } 329 } 330 331 @Override onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)332 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 333 super.onTextChanged(text, start, lengthBefore, lengthAfter); 334 boolean useHelper = mTextHelper != null && !SDK_LEVEL_SUPPORTS_AUTOSIZE 335 && mTextHelper.isAutoSizeEnabled(); 336 if (useHelper) { 337 mTextHelper.autoSizeText(); 338 } 339 } 340 341 /** 342 * This should be accessed via 343 * {@link androidx.core.widget.TextViewCompat#setAutoSizeTextTypeWithDefaults( 344 *TextView, int)} 345 * 346 */ 347 @RestrictTo(LIBRARY_GROUP_PREFIX) 348 @Override setAutoSizeTextTypeWithDefaults( @extViewCompat.AutoSizeTextType int autoSizeTextType)349 public void setAutoSizeTextTypeWithDefaults( 350 @TextViewCompat.AutoSizeTextType int autoSizeTextType) { 351 if (SDK_LEVEL_SUPPORTS_AUTOSIZE) { 352 getSuperCaller().setAutoSizeTextTypeWithDefaults(autoSizeTextType); 353 } else { 354 if (mTextHelper != null) { 355 mTextHelper.setAutoSizeTextTypeWithDefaults(autoSizeTextType); 356 } 357 } 358 } 359 360 /** 361 * This should be accessed via 362 * {@link androidx.core.widget.TextViewCompat#setAutoSizeTextTypeUniformWithConfiguration( 363 *TextView, int, int, int, int)} 364 * 365 */ 366 @RestrictTo(LIBRARY_GROUP_PREFIX) 367 @Override setAutoSizeTextTypeUniformWithConfiguration( int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)368 public void setAutoSizeTextTypeUniformWithConfiguration( 369 int autoSizeMinTextSize, 370 int autoSizeMaxTextSize, 371 int autoSizeStepGranularity, 372 int unit) throws IllegalArgumentException { 373 if (SDK_LEVEL_SUPPORTS_AUTOSIZE) { 374 getSuperCaller().setAutoSizeTextTypeUniformWithConfiguration(autoSizeMinTextSize, 375 autoSizeMaxTextSize, autoSizeStepGranularity, unit); 376 } else { 377 if (mTextHelper != null) { 378 mTextHelper.setAutoSizeTextTypeUniformWithConfiguration( 379 autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit); 380 } 381 } 382 } 383 384 /** 385 * This should be accessed via 386 * {@link androidx.core.widget.TextViewCompat#setAutoSizeTextTypeUniformWithPresetSizes( 387 *TextView, int[], int)} 388 * 389 */ 390 @RestrictTo(LIBRARY_GROUP_PREFIX) 391 @Override setAutoSizeTextTypeUniformWithPresetSizes(int @NonNull [] presetSizes, int unit)392 public void setAutoSizeTextTypeUniformWithPresetSizes(int @NonNull [] presetSizes, int unit) 393 throws IllegalArgumentException { 394 if (SDK_LEVEL_SUPPORTS_AUTOSIZE) { 395 getSuperCaller().setAutoSizeTextTypeUniformWithPresetSizes(presetSizes, unit); 396 } else { 397 if (mTextHelper != null) { 398 mTextHelper.setAutoSizeTextTypeUniformWithPresetSizes(presetSizes, unit); 399 } 400 } 401 } 402 403 /** 404 * This should be accessed via 405 * {@link androidx.core.widget.TextViewCompat#getAutoSizeTextType(TextView)} 406 * 407 */ 408 @RestrictTo(LIBRARY_GROUP_PREFIX) 409 @Override 410 @TextViewCompat.AutoSizeTextType 411 // Suppress lint error for TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM [WrongConstant] 412 @SuppressLint("WrongConstant") getAutoSizeTextType()413 public int getAutoSizeTextType() { 414 if (SDK_LEVEL_SUPPORTS_AUTOSIZE) { 415 return getSuperCaller().getAutoSizeTextType() == TextView 416 .AUTO_SIZE_TEXT_TYPE_UNIFORM 417 ? TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM 418 : TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE; 419 } else { 420 if (mTextHelper != null) { 421 return mTextHelper.getAutoSizeTextType(); 422 } 423 } 424 return TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE; 425 } 426 427 /** 428 * This should be accessed via 429 * {@link androidx.core.widget.TextViewCompat#getAutoSizeStepGranularity(TextView)} 430 * 431 */ 432 @RestrictTo(LIBRARY_GROUP_PREFIX) 433 @Override getAutoSizeStepGranularity()434 public int getAutoSizeStepGranularity() { 435 if (SDK_LEVEL_SUPPORTS_AUTOSIZE) { 436 return getSuperCaller().getAutoSizeStepGranularity(); 437 } else { 438 if (mTextHelper != null) { 439 return mTextHelper.getAutoSizeStepGranularity(); 440 } 441 } 442 return -1; 443 } 444 445 /** 446 * This should be accessed via 447 * {@link androidx.core.widget.TextViewCompat#getAutoSizeMinTextSize(TextView)} 448 * 449 */ 450 @RestrictTo(LIBRARY_GROUP_PREFIX) 451 @Override getAutoSizeMinTextSize()452 public int getAutoSizeMinTextSize() { 453 if (SDK_LEVEL_SUPPORTS_AUTOSIZE) { 454 return getSuperCaller().getAutoSizeMinTextSize(); 455 } else { 456 if (mTextHelper != null) { 457 return mTextHelper.getAutoSizeMinTextSize(); 458 } 459 } 460 return -1; 461 } 462 463 /** 464 * This should be accessed via 465 * {@link androidx.core.widget.TextViewCompat#getAutoSizeMaxTextSize(TextView)} 466 * 467 */ 468 @RestrictTo(LIBRARY_GROUP_PREFIX) 469 @Override getAutoSizeMaxTextSize()470 public int getAutoSizeMaxTextSize() { 471 if (SDK_LEVEL_SUPPORTS_AUTOSIZE) { 472 return getSuperCaller().getAutoSizeMaxTextSize(); 473 } else { 474 if (mTextHelper != null) { 475 return mTextHelper.getAutoSizeMaxTextSize(); 476 } 477 } 478 return -1; 479 } 480 481 /** 482 * This should be accessed via 483 * {@link androidx.core.widget.TextViewCompat#getAutoSizeTextAvailableSizes(TextView)} 484 * 485 */ 486 @RestrictTo(LIBRARY_GROUP_PREFIX) 487 @Override getAutoSizeTextAvailableSizes()488 public int[] getAutoSizeTextAvailableSizes() { 489 if (SDK_LEVEL_SUPPORTS_AUTOSIZE) { 490 return getSuperCaller().getAutoSizeTextAvailableSizes(); 491 } else { 492 if (mTextHelper != null) { 493 return mTextHelper.getAutoSizeTextAvailableSizes(); 494 } 495 } 496 return new int[0]; 497 } 498 499 @Override onCreateInputConnection(EditorInfo outAttrs)500 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 501 InputConnection ic = super.onCreateInputConnection(outAttrs); 502 mTextHelper.populateSurroundingTextIfNeeded(this, ic, outAttrs); 503 return AppCompatHintHelper.onCreateInputConnection(ic, outAttrs, this); 504 } 505 506 @Override setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)507 public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) { 508 if (Build.VERSION.SDK_INT >= 28) { 509 getSuperCaller().setFirstBaselineToTopHeight(firstBaselineToTopHeight); 510 } else { 511 TextViewCompat.setFirstBaselineToTopHeight(this, firstBaselineToTopHeight); 512 } 513 } 514 515 @Override setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)516 public void setLastBaselineToBottomHeight( 517 @Px @IntRange(from = 0) int lastBaselineToBottomHeight) { 518 if (Build.VERSION.SDK_INT >= 28) { 519 getSuperCaller().setLastBaselineToBottomHeight(lastBaselineToBottomHeight); 520 } else { 521 TextViewCompat.setLastBaselineToBottomHeight(this, 522 lastBaselineToBottomHeight); 523 } 524 } 525 526 @Override getFirstBaselineToTopHeight()527 public int getFirstBaselineToTopHeight() { 528 return TextViewCompat.getFirstBaselineToTopHeight(this); 529 } 530 531 @Override getLastBaselineToBottomHeight()532 public int getLastBaselineToBottomHeight() { 533 return TextViewCompat.getLastBaselineToBottomHeight(this); 534 } 535 536 @Override setLineHeight(@x @ntRangefrom = 0) int lineHeight)537 public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { 538 TextViewCompat.setLineHeight(this, lineHeight); 539 } 540 541 @Override setLineHeight(int unit, @FloatRange(from = 0) float lineHeight)542 public void setLineHeight(int unit, @FloatRange(from = 0) float lineHeight) { 543 if (Build.VERSION.SDK_INT >= 34) { 544 getSuperCaller().setLineHeight(unit, lineHeight); 545 } else { 546 TextViewCompat.setLineHeight(this, unit, lineHeight); 547 } 548 } 549 550 /** 551 * See 552 * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)} 553 */ 554 @Override setCustomSelectionActionModeCallback( ActionMode.@ullable Callback actionModeCallback)555 public void setCustomSelectionActionModeCallback( 556 ActionMode.@Nullable Callback actionModeCallback) { 557 super.setCustomSelectionActionModeCallback( 558 TextViewCompat.wrapCustomSelectionActionModeCallback(this, actionModeCallback)); 559 } 560 561 @Override getCustomSelectionActionModeCallback()562 public ActionMode.@Nullable Callback getCustomSelectionActionModeCallback() { 563 return TextViewCompat.unwrapCustomSelectionActionModeCallback( 564 super.getCustomSelectionActionModeCallback()); 565 } 566 567 /** 568 * Gets the parameters for text layout precomputation, for use with 569 * {@link PrecomputedTextCompat}. 570 * 571 * @return a current {@link PrecomputedTextCompat.Params} 572 * @see PrecomputedTextCompat 573 */ getTextMetricsParamsCompat()574 public PrecomputedTextCompat.@NonNull Params getTextMetricsParamsCompat() { 575 return TextViewCompat.getTextMetricsParams(this); 576 } 577 578 /** 579 * Apply the text layout parameter. 580 * 581 * Update the TextView parameters to be compatible with {@link PrecomputedTextCompat.Params}. 582 * 583 * @see PrecomputedTextCompat 584 */ setTextMetricsParamsCompat(PrecomputedTextCompat.@onNull Params params)585 public void setTextMetricsParamsCompat(PrecomputedTextCompat.@NonNull Params params) { 586 TextViewCompat.setTextMetricsParams(this, params); 587 } 588 589 /** 590 * Sets the PrecomputedTextCompat to the TextView. 591 * 592 * If the given PrecomputeTextCompat is not compatible with textView, throws an 593 * IllegalArgumentException. 594 * 595 * @param precomputed the precomputed text 596 * @throws IllegalArgumentException if precomputed text is not compatible with textView. 597 */ setPrecomputedText(@onNull PrecomputedTextCompat precomputed)598 public void setPrecomputedText(@NonNull PrecomputedTextCompat precomputed) { 599 TextViewCompat.setPrecomputedText(this, precomputed); 600 } 601 consumeTextFutureAndSetBlocking()602 private void consumeTextFutureAndSetBlocking() { 603 if (mPrecomputedTextFuture != null) { 604 try { 605 Future<PrecomputedTextCompat> future = mPrecomputedTextFuture; 606 mPrecomputedTextFuture = null; 607 TextViewCompat.setPrecomputedText(this, future.get()); 608 } catch (InterruptedException | ExecutionException e) { 609 // ignore 610 } 611 } 612 } 613 614 @Override getText()615 public CharSequence getText() { 616 consumeTextFutureAndSetBlocking(); 617 return super.getText(); 618 } 619 620 /** 621 * Sets the {@link TextClassifier} for this TextView. 622 */ 623 @Override 624 @RequiresApi(api = 26) setTextClassifier(@ullable TextClassifier textClassifier)625 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 626 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P || mTextClassifierHelper == null) { 627 getSuperCaller().setTextClassifier(textClassifier); 628 return; 629 } 630 mTextClassifierHelper.setTextClassifier(textClassifier); 631 } 632 633 /** 634 * Returns the {@link TextClassifier} used by this TextView. 635 * If no TextClassifier has been set, this TextView uses the default set by the 636 * {@link android.view.textclassifier.TextClassificationManager}. 637 */ 638 @Override 639 @RequiresApi(api = 26) getTextClassifier()640 public @NonNull TextClassifier getTextClassifier() { 641 // The null check is necessary because getTextClassifier is called when we are invoking 642 // the super class's constructor. 643 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P || mTextClassifierHelper == null) { 644 return getSuperCaller().getTextClassifier(); 645 } 646 return mTextClassifierHelper.getTextClassifier(); 647 } 648 649 /** 650 * Set the precomputed text future. 651 * 652 * This method sets future of the precomputed text instead of immediately applying text to the 653 * TextView. Anything layout related property changes, text size, typeface, letter spacing, etc 654 * after this method call will causes IllegalArgumentException during View measurement. 655 * 656 * See {@link PrecomputedTextCompat#getTextFuture} for more detail. 657 * 658 * @param future a future for the precomputed text 659 * @see PrecomputedTextCompat#getTextFuture 660 */ setTextFuture(@ullable Future<PrecomputedTextCompat> future)661 public void setTextFuture(@Nullable Future<PrecomputedTextCompat> future) { 662 mPrecomputedTextFuture = future; 663 if (future != null) { 664 requestLayout(); 665 } 666 } 667 668 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)669 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 670 consumeTextFutureAndSetBlocking(); 671 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 672 } 673 674 @Override setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)675 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top, 676 @Nullable Drawable right, @Nullable Drawable bottom) { 677 super.setCompoundDrawables(left, top, right, bottom); 678 if (mTextHelper != null) { 679 mTextHelper.onSetCompoundDrawables(); 680 } 681 } 682 683 @Override setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)684 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top, 685 @Nullable Drawable end, @Nullable Drawable bottom) { 686 super.setCompoundDrawablesRelative(start, top, end, bottom); 687 if (mTextHelper != null) { 688 mTextHelper.onSetCompoundDrawables(); 689 } 690 } 691 692 @Override setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)693 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left, 694 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { 695 super.setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom); 696 if (mTextHelper != null) { 697 mTextHelper.onSetCompoundDrawables(); 698 } 699 } 700 701 @Override setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom)702 public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) { 703 final Context context = getContext(); 704 setCompoundDrawablesWithIntrinsicBounds( 705 left != 0 ? AppCompatResources.getDrawable(context, left) : null, 706 top != 0 ? AppCompatResources.getDrawable(context, top) : null, 707 right != 0 ? AppCompatResources.getDrawable(context, right) : null, 708 bottom != 0 ? AppCompatResources.getDrawable(context, bottom) : null); 709 if (mTextHelper != null) { 710 mTextHelper.onSetCompoundDrawables(); 711 } 712 } 713 714 @Override setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)715 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start, 716 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { 717 super.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom); 718 if (mTextHelper != null) { 719 mTextHelper.onSetCompoundDrawables(); 720 } 721 } 722 723 @Override setCompoundDrawablesRelativeWithIntrinsicBounds( int start, int top, int end, int bottom)724 public void setCompoundDrawablesRelativeWithIntrinsicBounds( 725 int start, int top, int end, int bottom) { 726 final Context context = getContext(); 727 setCompoundDrawablesRelativeWithIntrinsicBounds( 728 start != 0 ? AppCompatResources.getDrawable(context, start) : null, 729 top != 0 ? AppCompatResources.getDrawable(context, top) : null, 730 end != 0 ? AppCompatResources.getDrawable(context, end) : null, 731 bottom != 0 ? AppCompatResources.getDrawable(context, bottom) : null); 732 if (mTextHelper != null) { 733 mTextHelper.onSetCompoundDrawables(); 734 } 735 } 736 737 /** 738 * This should be accessed via 739 * {@link androidx.core.widget.TextViewCompat#getCompoundDrawableTintList(TextView)} 740 * 741 * @return the tint applied to the compound drawables 742 * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTint 743 * @see #setSupportCompoundDrawablesTintList(ColorStateList) 744 * 745 */ 746 @Override 747 @RestrictTo(LIBRARY_GROUP_PREFIX) getSupportCompoundDrawablesTintList()748 public @Nullable ColorStateList getSupportCompoundDrawablesTintList() { 749 return mTextHelper.getCompoundDrawableTintList(); 750 } 751 752 /** 753 * This should be accessed via {@link 754 * androidx.core.widget.TextViewCompat#setCompoundDrawableTintList(TextView, ColorStateList)} 755 * 756 * Applies a tint to the compound drawables. Does not modify the current tint mode, which is 757 * {@link PorterDuff.Mode#SRC_IN} by default. 758 * <p> 759 * Subsequent calls to {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} and 760 * related methods will automatically mutate the drawables and apply the specified tint and tint 761 * mode using {@link Drawable#setTintList(ColorStateList)}. 762 * 763 * @param tintList the tint to apply, may be {@code null} to clear tint 764 * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTint 765 * @see #getSupportCompoundDrawablesTintList() 766 * 767 */ 768 @Override 769 @RestrictTo(LIBRARY_GROUP_PREFIX) setSupportCompoundDrawablesTintList(@ullable ColorStateList tintList)770 public void setSupportCompoundDrawablesTintList(@Nullable ColorStateList tintList) { 771 mTextHelper.setCompoundDrawableTintList(tintList); 772 mTextHelper.applyCompoundDrawablesTints(); 773 } 774 775 /** 776 * This should be accessed via 777 * {@link androidx.core.widget.TextViewCompat#getCompoundDrawableTintMode(TextView)} 778 * 779 * Returns the blending mode used to apply the tint to the compound drawables, if specified. 780 * 781 * @return the blending mode used to apply the tint to the compound drawables 782 * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTintMode 783 * @see #setSupportCompoundDrawablesTintMode(PorterDuff.Mode) 784 * 785 */ 786 @Override 787 @RestrictTo(LIBRARY_GROUP_PREFIX) getSupportCompoundDrawablesTintMode()788 public PorterDuff.@Nullable Mode getSupportCompoundDrawablesTintMode() { 789 return mTextHelper.getCompoundDrawableTintMode(); 790 } 791 792 /** 793 * This should be accessed via {@link 794 * androidx.core.widget.TextViewCompat#setCompoundDrawableTintMode(TextView, PorterDuff.Mode)} 795 * 796 * Specifies the blending mode used to apply the tint specified by 797 * {@link #setSupportCompoundDrawablesTintList(ColorStateList)} to the compound drawables. The 798 * default mode is {@link PorterDuff.Mode#SRC_IN}. 799 * 800 * @param tintMode the blending mode used to apply the tint, may be {@code null} to clear tint 801 * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTintMode 802 * @see #setSupportCompoundDrawablesTintList(ColorStateList) 803 * 804 */ 805 @Override 806 @RestrictTo(LIBRARY_GROUP_PREFIX) setSupportCompoundDrawablesTintMode(PorterDuff.@ullable Mode tintMode)807 public void setSupportCompoundDrawablesTintMode(PorterDuff.@Nullable Mode tintMode) { 808 mTextHelper.setCompoundDrawableTintMode(tintMode); 809 mTextHelper.applyCompoundDrawablesTints(); 810 } 811 812 // Never call super.setTypeface directly, always use this or setTypefaceInternal 813 // See docs on setTypefaceInternal for the differences 814 @Override setTypeface(@ullable Typeface tf)815 public void setTypeface(@Nullable Typeface tf) { 816 mOriginalTypeface = tf; 817 setTypefaceInternal(tf); 818 } 819 820 /** 821 * Call this when setting the typeface in any way that the user didn't directly ask for 822 * (that is, any case where TextView itself does not call through to setTypeface or otherwise 823 * set its mOriginalTypeface). Otherwise, use {@link #setTypeface(Typeface)} (or something 824 * that calls it). 825 * <p> 826 * Calls the superclass setTypeface, but does not set mOriginalTypeface. 827 * Also tracks what we set it to, in order to detect when it's been changed out from under us 828 * via modifying the Paint object directly. 829 * This isn't officially supported ({@link TextView#getPaint()} specifically says not to modify 830 * it), but at least one app is known to have done this, so we're providing best-effort support. 831 */ setTypefaceInternal(@ullable Typeface tf)832 private void setTypefaceInternal(@Nullable Typeface tf) { 833 mLastKnownTypefaceSetOnPaint = tf; 834 super.setTypeface(tf); 835 } 836 837 @Override 838 // Code inspection reveals that the superclass method can return null. 839 @SuppressWarnings("InvalidNullabilityOverride") getTypeface()840 public @Nullable Typeface getTypeface() { 841 return mOriginalTypeface; 842 } 843 844 @Override setTypeface(@ullable Typeface tf, int style)845 public void setTypeface(@Nullable Typeface tf, int style) { 846 if (mIsSetTypefaceProcessing) { 847 // b/151782655 848 // Some device up to API19 recursively calls setTypeface. To avoid infinity recursive 849 // setTypeface call, exit if we know this is re-entrant call. 850 // TODO(nona): Remove this once Android X minSdkVersion moves to API21. 851 return; 852 } 853 final Typeface finalTypeface; 854 if (tf != null && style > 0) { 855 finalTypeface = TypefaceCompat.create(getContext(), tf, style); 856 } else { 857 finalTypeface = tf; 858 } 859 860 mIsSetTypefaceProcessing = true; 861 try { 862 super.setTypeface(finalTypeface, style); 863 } finally { 864 mIsSetTypefaceProcessing = false; 865 } 866 867 } 868 869 @Override onDetachedFromWindow()870 protected void onDetachedFromWindow() { 871 super.onDetachedFromWindow(); 872 if (Build.VERSION.SDK_INT >= 30 && Build.VERSION.SDK_INT < 33 && onCheckIsTextEditor()) { 873 final InputMethodManager imm = (InputMethodManager) getContext().getSystemService( 874 Context.INPUT_METHOD_SERVICE); 875 // If the AppCompatTextView is editable, user can input text on it. 876 // Calling isActive() here implied a checkFocus() call to update the active served 877 // view for input method. This is a backport for mServedView was detached, but the 878 // next served view gets mistakenly cleared as well. 879 // https://android.googlesource.com/platform/frameworks/base/+/734613a500fb 880 imm.isActive(this); 881 } 882 } 883 884 @UiThread 885 @RequiresApi(api = 26) getSuperCaller()886 SuperCaller getSuperCaller() { 887 if (mSuperCaller == null) { 888 if (Build.VERSION.SDK_INT >= 34) { 889 mSuperCaller = new SuperCallerApi34(); 890 } else if (Build.VERSION.SDK_INT >= 28) { 891 mSuperCaller = new SuperCallerApi28(); 892 } else if (Build.VERSION.SDK_INT >= 26) { 893 mSuperCaller = new SuperCallerApi26(); 894 } 895 } 896 return mSuperCaller; 897 } 898 899 900 901 private interface SuperCaller { 902 // api 26 getAutoSizeMaxTextSize()903 int getAutoSizeMaxTextSize(); getAutoSizeMinTextSize()904 int getAutoSizeMinTextSize(); getAutoSizeStepGranularity()905 int getAutoSizeStepGranularity(); getAutoSizeTextAvailableSizes()906 int[] getAutoSizeTextAvailableSizes(); getAutoSizeTextType()907 int getAutoSizeTextType(); getTextClassifier()908 TextClassifier getTextClassifier(); setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)909 void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, 910 int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit); setAutoSizeTextTypeUniformWithPresetSizes(int[] presetSizes, int unit)911 void setAutoSizeTextTypeUniformWithPresetSizes(int[] presetSizes, int unit); setAutoSizeTextTypeWithDefaults(int autoSizeTextType)912 void setAutoSizeTextTypeWithDefaults(int autoSizeTextType); setTextClassifier(@ullable TextClassifier textClassifier)913 void setTextClassifier(@Nullable TextClassifier textClassifier); 914 915 // api 28 setFirstBaselineToTopHeight(@x int firstBaselineToTopHeight)916 void setFirstBaselineToTopHeight(@Px int firstBaselineToTopHeight); setLastBaselineToBottomHeight(@x int lastBaselineToBottomHeight)917 void setLastBaselineToBottomHeight(@Px int lastBaselineToBottomHeight); 918 919 // api 34 setLineHeight(int unit, @FloatRange(from = 0) float lineHeight)920 void setLineHeight(int unit, @FloatRange(from = 0) float lineHeight); 921 } 922 923 @RequiresApi(api = 26) 924 class SuperCallerApi26 implements SuperCaller { 925 @Override getAutoSizeMaxTextSize()926 public int getAutoSizeMaxTextSize() { 927 return AppCompatTextView.super.getAutoSizeMaxTextSize(); 928 } 929 930 @Override getAutoSizeMinTextSize()931 public int getAutoSizeMinTextSize() { 932 return AppCompatTextView.super.getAutoSizeMinTextSize(); 933 } 934 935 @Override getAutoSizeStepGranularity()936 public int getAutoSizeStepGranularity() { 937 return AppCompatTextView.super.getAutoSizeStepGranularity(); 938 } 939 940 @Override getAutoSizeTextAvailableSizes()941 public int[] getAutoSizeTextAvailableSizes() { 942 return AppCompatTextView.super.getAutoSizeTextAvailableSizes(); 943 } 944 945 @Override getAutoSizeTextType()946 public int getAutoSizeTextType() { 947 return AppCompatTextView.super.getAutoSizeTextType(); 948 } 949 950 @Override getTextClassifier()951 public TextClassifier getTextClassifier() { 952 return AppCompatTextView.super.getTextClassifier(); 953 } 954 955 @Override setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)956 public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, 957 int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) { 958 AppCompatTextView.super.setAutoSizeTextTypeUniformWithConfiguration(autoSizeMinTextSize, 959 autoSizeMaxTextSize, autoSizeStepGranularity, unit); 960 } 961 962 @Override setAutoSizeTextTypeUniformWithPresetSizes(int[] presetSizes, int unit)963 public void setAutoSizeTextTypeUniformWithPresetSizes(int[] presetSizes, int unit) { 964 AppCompatTextView.super.setAutoSizeTextTypeUniformWithPresetSizes(presetSizes, unit); 965 } 966 967 @Override setAutoSizeTextTypeWithDefaults(int autoSizeTextType)968 public void setAutoSizeTextTypeWithDefaults(int autoSizeTextType) { 969 AppCompatTextView.super.setAutoSizeTextTypeWithDefaults(autoSizeTextType); 970 } 971 972 @Override setTextClassifier(@ullable TextClassifier textClassifier)973 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 974 AppCompatTextView.super.setTextClassifier(textClassifier); 975 } 976 977 @Override setFirstBaselineToTopHeight(int firstBaselineToTopHeight)978 public void setFirstBaselineToTopHeight(int firstBaselineToTopHeight) {} 979 980 @Override setLastBaselineToBottomHeight(int lastBaselineToBottomHeight)981 public void setLastBaselineToBottomHeight(int lastBaselineToBottomHeight) {} 982 983 @Override setLineHeight(int unit, float lineHeight)984 public void setLineHeight(int unit, float lineHeight) {} 985 } 986 987 @RequiresApi(api = 28) 988 class SuperCallerApi28 extends SuperCallerApi26 { 989 990 @Override setFirstBaselineToTopHeight(@x int firstBaselineToTopHeight)991 public void setFirstBaselineToTopHeight(@Px int firstBaselineToTopHeight) { 992 AppCompatTextView.super.setFirstBaselineToTopHeight(firstBaselineToTopHeight); 993 } 994 995 @Override setLastBaselineToBottomHeight(@x int lastBaselineToBottomHeight)996 public void setLastBaselineToBottomHeight(@Px int lastBaselineToBottomHeight) { 997 AppCompatTextView.super.setLastBaselineToBottomHeight(lastBaselineToBottomHeight); 998 } 999 } 1000 1001 @RequiresApi(api = 34) 1002 class SuperCallerApi34 extends SuperCallerApi28 { 1003 @Override setLineHeight(int unit, float lineHeight)1004 public void setLineHeight(int unit, float lineHeight) { 1005 AppCompatTextView.super.setLineHeight(unit, lineHeight); 1006 } 1007 } 1008 } 1009