Loading core/java/android/view/inputmethod/TextAppearanceInfo.java +67 −0 Original line number Diff line number Diff line Loading @@ -31,7 +31,10 @@ import android.inputmethodservice.InputMethodService; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; import android.text.Spanned; import android.text.TextPaint; import android.text.method.TransformationMethod; import android.text.style.CharacterStyle; import android.widget.TextView; import java.util.Objects; Loading Loading @@ -182,6 +185,70 @@ public final class TextAppearanceInfo implements Parcelable { mLinkTextColor = builder.mLinkTextColor; } /** * Creates a new instance of {@link TextAppearanceInfo} by extracting text appearance from the * character before cursor in the target {@link TextView}. * @param textView the target {@link TextView}. * @return the new instance of {@link TextAppearanceInfo}. * @hide */ @NonNull public static TextAppearanceInfo createFromTextView(@NonNull TextView textView) { final int selectionStart = textView.getSelectionStart(); final CharSequence text = textView.getText(); TextPaint textPaint = new TextPaint(); textPaint.set(textView.getPaint()); // Copy from textView if (text instanceof Spanned && text.length() > 0 && selectionStart > 0) { // Extract the CharacterStyle spans that changes text appearance in the character before // cursor. Spanned spannedText = (Spanned) text; int lastCh = selectionStart - 1; CharacterStyle[] spans = spannedText.getSpans(lastCh, lastCh, CharacterStyle.class); if (spans != null) { for (CharacterStyle span: spans) { // Exclude spans that end at lastCh if (spannedText.getSpanStart(span) <= lastCh && lastCh < spannedText.getSpanEnd(span)) { span.updateDrawState(textPaint); // Override the TextPaint } } } } Typeface typeface = textPaint.getTypeface(); String systemFontFamilyName = null; int textWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED; int textStyle = Typeface.NORMAL; if (typeface != null) { systemFontFamilyName = typeface.getSystemFontFamilyName(); textWeight = typeface.getWeight(); textStyle = typeface.getStyle(); } TextAppearanceInfo.Builder builder = new TextAppearanceInfo.Builder(); builder.setTextSize(textPaint.getTextSize()) .setTextLocales(textPaint.getTextLocales()) .setSystemFontFamilyName(systemFontFamilyName) .setTextFontWeight(textWeight) .setTextStyle(textStyle) .setShadowDx(textPaint.getShadowLayerDx()) .setShadowDy(textPaint.getShadowLayerDy()) .setShadowRadius(textPaint.getShadowLayerRadius()) .setShadowColor(textPaint.getShadowLayerColor()) .setElegantTextHeight(textPaint.isElegantTextHeight()) .setLetterSpacing(textPaint.getLetterSpacing()) .setFontFeatureSettings(textPaint.getFontFeatureSettings()) .setFontVariationSettings(textPaint.getFontVariationSettings()) .setTextScaleX(textPaint.getTextScaleX()) .setTextColor(textPaint.getColor()) .setLinkTextColor(textPaint.linkColor) .setAllCaps(textView.isAllCaps()) .setFallbackLineSpacing(textView.isFallbackLineSpacing()) .setLineBreakStyle(textView.getLineBreakStyle()) .setLineBreakWordStyle(textView.getLineBreakWordStyle()) .setHighlightTextColor(textView.getHighlightColor()) .setHintTextColor(textView.getCurrentHintTextColor()); return builder.build(); } @Override public int describeContents() { return 0; Loading core/java/android/widget/Editor.java +1 −39 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static android.widget.TextView.ACCESSIBILITY_ACTION_SMART_START_ID; import android.R; import android.animation.ValueAnimator; import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; Loading @@ -38,7 +37,6 @@ import android.content.UndoOperation; import android.content.UndoOwner; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; Loading @@ -51,10 +49,8 @@ import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.RenderNode; import android.graphics.Typeface; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.fonts.FontStyle; import android.os.Build; import android.os.Bundle; import android.os.LocaleList; Loading Loading @@ -4774,41 +4770,7 @@ public class Editor { } if (includeTextAppearance) { Typeface typeface = mTextView.getPaint().getTypeface(); String systemFontFamilyName = null; int textFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED; if (typeface != null) { systemFontFamilyName = typeface.getSystemFontFamilyName(); textFontWeight = typeface.getWeight(); } ColorStateList linkTextColors = mTextView.getLinkTextColors(); @ColorInt int linkTextColor = linkTextColors != null ? linkTextColors.getDefaultColor() : 0; TextAppearanceInfo.Builder appearanceBuilder = new TextAppearanceInfo.Builder(); appearanceBuilder.setTextSize(mTextView.getTextSize()) .setTextLocales(mTextView.getTextLocales()) .setSystemFontFamilyName(systemFontFamilyName) .setTextFontWeight(textFontWeight) .setTextStyle(mTextView.getTypefaceStyle()) .setAllCaps(mTextView.isAllCaps()) .setShadowDx(mTextView.getShadowDx()) .setShadowDy(mTextView.getShadowDy()) .setShadowRadius(mTextView.getShadowRadius()) .setShadowColor(mTextView.getShadowColor()) .setElegantTextHeight(mTextView.isElegantTextHeight()) .setFallbackLineSpacing(mTextView.isFallbackLineSpacing()) .setLetterSpacing(mTextView.getLetterSpacing()) .setFontFeatureSettings(mTextView.getFontFeatureSettings()) .setFontVariationSettings(mTextView.getFontVariationSettings()) .setLineBreakStyle(mTextView.getLineBreakStyle()) .setLineBreakWordStyle(mTextView.getLineBreakWordStyle()) .setTextScaleX(mTextView.getTextScaleX()) .setHighlightTextColor(mTextView.getHighlightColor()) .setTextColor(mTextView.getCurrentTextColor()) .setHintTextColor(mTextView.getCurrentHintTextColor()) .setLinkTextColor(linkTextColor); builder.setTextAppearanceInfo(appearanceBuilder.build()); builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(mTextView)); } imm.updateCursorAnchorInfo(mTextView, builder.build()); Loading core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java 0 → 100644 +281 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view.inputmethod; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Typeface; import android.graphics.fonts.FontStyle; import android.graphics.text.LineBreakConfig; import android.os.LocaleList; import android.os.Parcel; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextPaint; import android.text.style.AbsoluteSizeSpan; import android.text.style.ForegroundColorSpan; import android.text.style.ScaleXSpan; import android.text.style.StyleSpan; import android.text.style.TypefaceSpan; import android.view.ViewGroup; import android.widget.EditText; import androidx.annotation.NonNull; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) public class TextAppearanceInfoTest { private static final float EPSILON = 0.0000001f; private static final String TEST_TEXT = "Happy birthday!"; private static final float TEXT_SIZE = 16.5f; private static final LocaleList TEXT_LOCALES = LocaleList.forLanguageTags("en,ja"); private static final String FONT_FAMILY_NAME = "sans-serif"; private static final int TEXT_WEIGHT = FontStyle.FONT_WEIGHT_MEDIUM; private static final int TEXT_STYLE = Typeface.ITALIC; private static final boolean ALL_CAPS = true; private static final float SHADOW_DX = 2.0f; private static final float SHADOW_DY = 2.0f; private static final float SHADOW_RADIUS = 2.0f; private static final int SHADOW_COLOR = Color.GRAY; private static final boolean ELEGANT_TEXT_HEIGHT = true; private static final boolean FALLBACK_LINE_SPACING = true; private static final float LETTER_SPACING = 5.0f; private static final String FONT_FEATURE_SETTINGS = "smcp"; private static final String FONT_VARIATION_SETTINGS = "'wdth' 1.0"; private static final int LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_LOOSE; private static final int LINE_BREAK_WORD_STYLE = LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE; private static final float TEXT_SCALEX = 1.5f; private static final int HIGHLIGHT_TEXT_COLOR = Color.YELLOW; private static final int TEXT_COLOR = Color.RED; private static final int HINT_TEXT_COLOR = Color.GREEN; private static final int LINK_TEXT_COLOR = Color.BLUE; private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); private final EditText mEditText = new EditText(mContext); private final SpannableStringBuilder mSpannableText = new SpannableStringBuilder(TEST_TEXT); private Canvas mCanvas; @Before public void setUp() { mEditText.setText(mSpannableText); mEditText.getPaint().setTextSize(TEXT_SIZE); mEditText.setTextLocales(TEXT_LOCALES); Typeface family = Typeface.create(FONT_FAMILY_NAME, Typeface.NORMAL); mEditText.setTypeface( Typeface.create(family, TEXT_WEIGHT, (TEXT_STYLE & Typeface.ITALIC) != 0)); mEditText.setAllCaps(ALL_CAPS); mEditText.setShadowLayer(SHADOW_RADIUS, SHADOW_DX, SHADOW_DY, SHADOW_COLOR); mEditText.setElegantTextHeight(ELEGANT_TEXT_HEIGHT); mEditText.setFallbackLineSpacing(FALLBACK_LINE_SPACING); mEditText.setLetterSpacing(LETTER_SPACING); mEditText.setFontFeatureSettings(FONT_FEATURE_SETTINGS); mEditText.setFontVariationSettings(FONT_VARIATION_SETTINGS); mEditText.setLineBreakStyle(LINE_BREAK_STYLE); mEditText.setLineBreakWordStyle(LINE_BREAK_WORD_STYLE); mEditText.setTextScaleX(TEXT_SCALEX); mEditText.setHighlightColor(HIGHLIGHT_TEXT_COLOR); mEditText.setTextColor(TEXT_COLOR); mEditText.setHintTextColor(HINT_TEXT_COLOR); mEditText.setLinkTextColor(LINK_TEXT_COLOR); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); mEditText.setLayoutParams(params); mEditText.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); Bitmap bitmap = Bitmap.createBitmap( Math.max(1, mEditText.getMeasuredWidth()), Math.max(1, mEditText.getMeasuredHeight()), Bitmap.Config.ARGB_8888); mEditText.layout(0, 0, mEditText.getMeasuredWidth(), mEditText.getMeasuredHeight()); mCanvas = new Canvas(bitmap); mEditText.draw(mCanvas); } @Test public void testCreateFromTextView_noSpan() { assertTextAppearanceInfoContentsEqual(TextAppearanceInfo.createFromTextView(mEditText)); } @Test public void testCreateFromTextView_withSpan1() { AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(30); mSpannableText.setSpan(sizeSpan, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.CYAN); mSpannableText.setSpan(colorSpan, 1, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); TypefaceSpan typefaceSpan = new TypefaceSpan("cursive"); mSpannableText.setSpan(typefaceSpan, 2, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mEditText.setText(mSpannableText); // |Happy birthday! mEditText.setSelection(0); TextAppearanceInfo info1 = TextAppearanceInfo.createFromTextView(mEditText); assertEquals(info1.getTextSize(), TEXT_SIZE, EPSILON); assertEquals(info1.getTextColor(), TEXT_COLOR); assertEquals(info1.getSystemFontFamilyName(), FONT_FAMILY_NAME); // H|appy birthday! mEditText.setSelection(1); TextAppearanceInfo info2 = TextAppearanceInfo.createFromTextView(mEditText); assertEquals(info2.getTextSize(), 30f, EPSILON); assertEquals(info2.getTextColor(), TEXT_COLOR); assertEquals(info2.getSystemFontFamilyName(), FONT_FAMILY_NAME); // Ha|ppy birthday! mEditText.setSelection(2); TextAppearanceInfo info3 = TextAppearanceInfo.createFromTextView(mEditText); assertEquals(info3.getTextSize(), 30f, EPSILON); assertEquals(info3.getTextColor(), Color.CYAN); assertEquals(info3.getSystemFontFamilyName(), FONT_FAMILY_NAME); // Ha[ppy birthday!] mEditText.setSelection(2, mSpannableText.length()); TextAppearanceInfo info4 = TextAppearanceInfo.createFromTextView(mEditText); assertEquals(info4.getTextSize(), 30f, EPSILON); assertEquals(info4.getTextColor(), Color.CYAN); assertEquals(info4.getSystemFontFamilyName(), FONT_FAMILY_NAME); // Happy| birthday! mEditText.setSelection(5); TextAppearanceInfo info5 = TextAppearanceInfo.createFromTextView(mEditText); assertEquals(info5.getTextSize(), 30f, EPSILON); assertEquals(info5.getTextColor(), Color.CYAN); assertEquals(info5.getSystemFontFamilyName(), "cursive"); } @Test public void testCreateFromTextView_withSpan2() { // aab| SpannableStringBuilder spannableText = new SpannableStringBuilder("aab"); AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(30); spannableText.setSpan(sizeSpan, 0, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE); ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.CYAN); spannableText.setSpan(colorSpan, 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); StyleSpan styleSpan = new StyleSpan(Typeface.BOLD); spannableText.setSpan(styleSpan, 1, 2, Spanned.SPAN_EXCLUSIVE_INCLUSIVE); TypefaceSpan typefaceSpan = new TypefaceSpan("cursive"); spannableText.setSpan(typefaceSpan, 3, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE); ScaleXSpan scaleXSpan = new ScaleXSpan(2.0f); spannableText.setSpan(scaleXSpan, 3, 3, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); mEditText.setText(spannableText); mEditText.setSelection(3); TextAppearanceInfo info = TextAppearanceInfo.createFromTextView(mEditText); // The character before cursor 'b' should only have an AbsoluteSizeSpan. assertEquals(info.getTextSize(), 30f, EPSILON); assertEquals(info.getTextColor(), TEXT_COLOR); assertEquals(info.getTextStyle(), TEXT_STYLE); assertEquals(info.getSystemFontFamilyName(), FONT_FAMILY_NAME); assertEquals(info.getTextScaleX(), TEXT_SCALEX, EPSILON); } @Test public void testCreateFromTextView_contradictorySpans() { // Set multiple contradictory spans AbsoluteSizeSpan sizeSpan1 = new AbsoluteSizeSpan(30); CustomForegroundColorSpan colorSpan1 = new CustomForegroundColorSpan(Color.BLUE); AbsoluteSizeSpan sizeSpan2 = new AbsoluteSizeSpan(10); CustomForegroundColorSpan colorSpan2 = new CustomForegroundColorSpan(Color.GREEN); mSpannableText.setSpan(sizeSpan1, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mSpannableText.setSpan(colorSpan1, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mSpannableText.setSpan(sizeSpan2, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mSpannableText.setSpan(colorSpan2, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mEditText.setText(mSpannableText); mEditText.draw(mCanvas); mEditText.setSelection(3); // Get a copy of the real TextPaint after setting the last span TextPaint realTextPaint = colorSpan2.lastTextPaint; assertNotNull(realTextPaint); TextAppearanceInfo info1 = TextAppearanceInfo.createFromTextView(mEditText); // Verify the real TextPaint equals the last span of multiple contradictory spans assertEquals(info1.getTextSize(), 10f, EPSILON); assertEquals(info1.getTextSize(), realTextPaint.getTextSize(), EPSILON); assertEquals(info1.getTextColor(), Color.GREEN); assertEquals(info1.getTextColor(), realTextPaint.getColor()); assertEquals(info1.getSystemFontFamilyName(), FONT_FAMILY_NAME); } private void assertTextAppearanceInfoContentsEqual(TextAppearanceInfo textAppearanceInfo) { assertEquals(textAppearanceInfo.getTextSize(), TEXT_SIZE, EPSILON); assertEquals(textAppearanceInfo.getTextLocales(), TEXT_LOCALES); assertEquals(textAppearanceInfo.getSystemFontFamilyName(), FONT_FAMILY_NAME); assertEquals(textAppearanceInfo.getTextFontWeight(), TEXT_WEIGHT); assertEquals(textAppearanceInfo.getTextStyle(), TEXT_STYLE); assertEquals(textAppearanceInfo.isAllCaps(), ALL_CAPS); assertEquals(textAppearanceInfo.getShadowRadius(), SHADOW_RADIUS, EPSILON); assertEquals(textAppearanceInfo.getShadowDx(), SHADOW_DX, EPSILON); assertEquals(textAppearanceInfo.getShadowDy(), SHADOW_DY, EPSILON); assertEquals(textAppearanceInfo.getShadowColor(), SHADOW_COLOR); assertEquals(textAppearanceInfo.isElegantTextHeight(), ELEGANT_TEXT_HEIGHT); assertEquals(textAppearanceInfo.isFallbackLineSpacing(), FALLBACK_LINE_SPACING); assertEquals(textAppearanceInfo.getLetterSpacing(), LETTER_SPACING, EPSILON); assertEquals(textAppearanceInfo.getFontFeatureSettings(), FONT_FEATURE_SETTINGS); assertEquals(textAppearanceInfo.getFontVariationSettings(), FONT_VARIATION_SETTINGS); assertEquals(textAppearanceInfo.getLineBreakStyle(), LINE_BREAK_STYLE); assertEquals(textAppearanceInfo.getLineBreakWordStyle(), LINE_BREAK_WORD_STYLE); assertEquals(textAppearanceInfo.getTextScaleX(), TEXT_SCALEX, EPSILON); assertEquals(textAppearanceInfo.getTextColor(), TEXT_COLOR); assertEquals(textAppearanceInfo.getHighlightTextColor(), HIGHLIGHT_TEXT_COLOR); assertEquals(textAppearanceInfo.getHintTextColor(), HINT_TEXT_COLOR); assertEquals(textAppearanceInfo.getLinkTextColor(), LINK_TEXT_COLOR); } static class CustomForegroundColorSpan extends ForegroundColorSpan { @Nullable public TextPaint lastTextPaint = null; CustomForegroundColorSpan(int color) { super(color); } CustomForegroundColorSpan(@NonNull Parcel src) { super(src); } @Override public void updateDrawState(@NonNull TextPaint tp) { super.updateDrawState(tp); // Copy the real TextPaint TextPaint tpCopy = new TextPaint(); tpCopy.set(tp); lastTextPaint = tpCopy; } } } Loading
core/java/android/view/inputmethod/TextAppearanceInfo.java +67 −0 Original line number Diff line number Diff line Loading @@ -31,7 +31,10 @@ import android.inputmethodservice.InputMethodService; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; import android.text.Spanned; import android.text.TextPaint; import android.text.method.TransformationMethod; import android.text.style.CharacterStyle; import android.widget.TextView; import java.util.Objects; Loading Loading @@ -182,6 +185,70 @@ public final class TextAppearanceInfo implements Parcelable { mLinkTextColor = builder.mLinkTextColor; } /** * Creates a new instance of {@link TextAppearanceInfo} by extracting text appearance from the * character before cursor in the target {@link TextView}. * @param textView the target {@link TextView}. * @return the new instance of {@link TextAppearanceInfo}. * @hide */ @NonNull public static TextAppearanceInfo createFromTextView(@NonNull TextView textView) { final int selectionStart = textView.getSelectionStart(); final CharSequence text = textView.getText(); TextPaint textPaint = new TextPaint(); textPaint.set(textView.getPaint()); // Copy from textView if (text instanceof Spanned && text.length() > 0 && selectionStart > 0) { // Extract the CharacterStyle spans that changes text appearance in the character before // cursor. Spanned spannedText = (Spanned) text; int lastCh = selectionStart - 1; CharacterStyle[] spans = spannedText.getSpans(lastCh, lastCh, CharacterStyle.class); if (spans != null) { for (CharacterStyle span: spans) { // Exclude spans that end at lastCh if (spannedText.getSpanStart(span) <= lastCh && lastCh < spannedText.getSpanEnd(span)) { span.updateDrawState(textPaint); // Override the TextPaint } } } } Typeface typeface = textPaint.getTypeface(); String systemFontFamilyName = null; int textWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED; int textStyle = Typeface.NORMAL; if (typeface != null) { systemFontFamilyName = typeface.getSystemFontFamilyName(); textWeight = typeface.getWeight(); textStyle = typeface.getStyle(); } TextAppearanceInfo.Builder builder = new TextAppearanceInfo.Builder(); builder.setTextSize(textPaint.getTextSize()) .setTextLocales(textPaint.getTextLocales()) .setSystemFontFamilyName(systemFontFamilyName) .setTextFontWeight(textWeight) .setTextStyle(textStyle) .setShadowDx(textPaint.getShadowLayerDx()) .setShadowDy(textPaint.getShadowLayerDy()) .setShadowRadius(textPaint.getShadowLayerRadius()) .setShadowColor(textPaint.getShadowLayerColor()) .setElegantTextHeight(textPaint.isElegantTextHeight()) .setLetterSpacing(textPaint.getLetterSpacing()) .setFontFeatureSettings(textPaint.getFontFeatureSettings()) .setFontVariationSettings(textPaint.getFontVariationSettings()) .setTextScaleX(textPaint.getTextScaleX()) .setTextColor(textPaint.getColor()) .setLinkTextColor(textPaint.linkColor) .setAllCaps(textView.isAllCaps()) .setFallbackLineSpacing(textView.isFallbackLineSpacing()) .setLineBreakStyle(textView.getLineBreakStyle()) .setLineBreakWordStyle(textView.getLineBreakWordStyle()) .setHighlightTextColor(textView.getHighlightColor()) .setHintTextColor(textView.getCurrentHintTextColor()); return builder.build(); } @Override public int describeContents() { return 0; Loading
core/java/android/widget/Editor.java +1 −39 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static android.widget.TextView.ACCESSIBILITY_ACTION_SMART_START_ID; import android.R; import android.animation.ValueAnimator; import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; Loading @@ -38,7 +37,6 @@ import android.content.UndoOperation; import android.content.UndoOwner; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; Loading @@ -51,10 +49,8 @@ import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.RenderNode; import android.graphics.Typeface; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.fonts.FontStyle; import android.os.Build; import android.os.Bundle; import android.os.LocaleList; Loading Loading @@ -4774,41 +4770,7 @@ public class Editor { } if (includeTextAppearance) { Typeface typeface = mTextView.getPaint().getTypeface(); String systemFontFamilyName = null; int textFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED; if (typeface != null) { systemFontFamilyName = typeface.getSystemFontFamilyName(); textFontWeight = typeface.getWeight(); } ColorStateList linkTextColors = mTextView.getLinkTextColors(); @ColorInt int linkTextColor = linkTextColors != null ? linkTextColors.getDefaultColor() : 0; TextAppearanceInfo.Builder appearanceBuilder = new TextAppearanceInfo.Builder(); appearanceBuilder.setTextSize(mTextView.getTextSize()) .setTextLocales(mTextView.getTextLocales()) .setSystemFontFamilyName(systemFontFamilyName) .setTextFontWeight(textFontWeight) .setTextStyle(mTextView.getTypefaceStyle()) .setAllCaps(mTextView.isAllCaps()) .setShadowDx(mTextView.getShadowDx()) .setShadowDy(mTextView.getShadowDy()) .setShadowRadius(mTextView.getShadowRadius()) .setShadowColor(mTextView.getShadowColor()) .setElegantTextHeight(mTextView.isElegantTextHeight()) .setFallbackLineSpacing(mTextView.isFallbackLineSpacing()) .setLetterSpacing(mTextView.getLetterSpacing()) .setFontFeatureSettings(mTextView.getFontFeatureSettings()) .setFontVariationSettings(mTextView.getFontVariationSettings()) .setLineBreakStyle(mTextView.getLineBreakStyle()) .setLineBreakWordStyle(mTextView.getLineBreakWordStyle()) .setTextScaleX(mTextView.getTextScaleX()) .setHighlightTextColor(mTextView.getHighlightColor()) .setTextColor(mTextView.getCurrentTextColor()) .setHintTextColor(mTextView.getCurrentHintTextColor()) .setLinkTextColor(linkTextColor); builder.setTextAppearanceInfo(appearanceBuilder.build()); builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(mTextView)); } imm.updateCursorAnchorInfo(mTextView, builder.build()); Loading
core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java 0 → 100644 +281 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view.inputmethod; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Typeface; import android.graphics.fonts.FontStyle; import android.graphics.text.LineBreakConfig; import android.os.LocaleList; import android.os.Parcel; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextPaint; import android.text.style.AbsoluteSizeSpan; import android.text.style.ForegroundColorSpan; import android.text.style.ScaleXSpan; import android.text.style.StyleSpan; import android.text.style.TypefaceSpan; import android.view.ViewGroup; import android.widget.EditText; import androidx.annotation.NonNull; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) public class TextAppearanceInfoTest { private static final float EPSILON = 0.0000001f; private static final String TEST_TEXT = "Happy birthday!"; private static final float TEXT_SIZE = 16.5f; private static final LocaleList TEXT_LOCALES = LocaleList.forLanguageTags("en,ja"); private static final String FONT_FAMILY_NAME = "sans-serif"; private static final int TEXT_WEIGHT = FontStyle.FONT_WEIGHT_MEDIUM; private static final int TEXT_STYLE = Typeface.ITALIC; private static final boolean ALL_CAPS = true; private static final float SHADOW_DX = 2.0f; private static final float SHADOW_DY = 2.0f; private static final float SHADOW_RADIUS = 2.0f; private static final int SHADOW_COLOR = Color.GRAY; private static final boolean ELEGANT_TEXT_HEIGHT = true; private static final boolean FALLBACK_LINE_SPACING = true; private static final float LETTER_SPACING = 5.0f; private static final String FONT_FEATURE_SETTINGS = "smcp"; private static final String FONT_VARIATION_SETTINGS = "'wdth' 1.0"; private static final int LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_LOOSE; private static final int LINE_BREAK_WORD_STYLE = LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE; private static final float TEXT_SCALEX = 1.5f; private static final int HIGHLIGHT_TEXT_COLOR = Color.YELLOW; private static final int TEXT_COLOR = Color.RED; private static final int HINT_TEXT_COLOR = Color.GREEN; private static final int LINK_TEXT_COLOR = Color.BLUE; private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); private final EditText mEditText = new EditText(mContext); private final SpannableStringBuilder mSpannableText = new SpannableStringBuilder(TEST_TEXT); private Canvas mCanvas; @Before public void setUp() { mEditText.setText(mSpannableText); mEditText.getPaint().setTextSize(TEXT_SIZE); mEditText.setTextLocales(TEXT_LOCALES); Typeface family = Typeface.create(FONT_FAMILY_NAME, Typeface.NORMAL); mEditText.setTypeface( Typeface.create(family, TEXT_WEIGHT, (TEXT_STYLE & Typeface.ITALIC) != 0)); mEditText.setAllCaps(ALL_CAPS); mEditText.setShadowLayer(SHADOW_RADIUS, SHADOW_DX, SHADOW_DY, SHADOW_COLOR); mEditText.setElegantTextHeight(ELEGANT_TEXT_HEIGHT); mEditText.setFallbackLineSpacing(FALLBACK_LINE_SPACING); mEditText.setLetterSpacing(LETTER_SPACING); mEditText.setFontFeatureSettings(FONT_FEATURE_SETTINGS); mEditText.setFontVariationSettings(FONT_VARIATION_SETTINGS); mEditText.setLineBreakStyle(LINE_BREAK_STYLE); mEditText.setLineBreakWordStyle(LINE_BREAK_WORD_STYLE); mEditText.setTextScaleX(TEXT_SCALEX); mEditText.setHighlightColor(HIGHLIGHT_TEXT_COLOR); mEditText.setTextColor(TEXT_COLOR); mEditText.setHintTextColor(HINT_TEXT_COLOR); mEditText.setLinkTextColor(LINK_TEXT_COLOR); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); mEditText.setLayoutParams(params); mEditText.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); Bitmap bitmap = Bitmap.createBitmap( Math.max(1, mEditText.getMeasuredWidth()), Math.max(1, mEditText.getMeasuredHeight()), Bitmap.Config.ARGB_8888); mEditText.layout(0, 0, mEditText.getMeasuredWidth(), mEditText.getMeasuredHeight()); mCanvas = new Canvas(bitmap); mEditText.draw(mCanvas); } @Test public void testCreateFromTextView_noSpan() { assertTextAppearanceInfoContentsEqual(TextAppearanceInfo.createFromTextView(mEditText)); } @Test public void testCreateFromTextView_withSpan1() { AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(30); mSpannableText.setSpan(sizeSpan, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.CYAN); mSpannableText.setSpan(colorSpan, 1, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); TypefaceSpan typefaceSpan = new TypefaceSpan("cursive"); mSpannableText.setSpan(typefaceSpan, 2, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mEditText.setText(mSpannableText); // |Happy birthday! mEditText.setSelection(0); TextAppearanceInfo info1 = TextAppearanceInfo.createFromTextView(mEditText); assertEquals(info1.getTextSize(), TEXT_SIZE, EPSILON); assertEquals(info1.getTextColor(), TEXT_COLOR); assertEquals(info1.getSystemFontFamilyName(), FONT_FAMILY_NAME); // H|appy birthday! mEditText.setSelection(1); TextAppearanceInfo info2 = TextAppearanceInfo.createFromTextView(mEditText); assertEquals(info2.getTextSize(), 30f, EPSILON); assertEquals(info2.getTextColor(), TEXT_COLOR); assertEquals(info2.getSystemFontFamilyName(), FONT_FAMILY_NAME); // Ha|ppy birthday! mEditText.setSelection(2); TextAppearanceInfo info3 = TextAppearanceInfo.createFromTextView(mEditText); assertEquals(info3.getTextSize(), 30f, EPSILON); assertEquals(info3.getTextColor(), Color.CYAN); assertEquals(info3.getSystemFontFamilyName(), FONT_FAMILY_NAME); // Ha[ppy birthday!] mEditText.setSelection(2, mSpannableText.length()); TextAppearanceInfo info4 = TextAppearanceInfo.createFromTextView(mEditText); assertEquals(info4.getTextSize(), 30f, EPSILON); assertEquals(info4.getTextColor(), Color.CYAN); assertEquals(info4.getSystemFontFamilyName(), FONT_FAMILY_NAME); // Happy| birthday! mEditText.setSelection(5); TextAppearanceInfo info5 = TextAppearanceInfo.createFromTextView(mEditText); assertEquals(info5.getTextSize(), 30f, EPSILON); assertEquals(info5.getTextColor(), Color.CYAN); assertEquals(info5.getSystemFontFamilyName(), "cursive"); } @Test public void testCreateFromTextView_withSpan2() { // aab| SpannableStringBuilder spannableText = new SpannableStringBuilder("aab"); AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(30); spannableText.setSpan(sizeSpan, 0, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE); ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.CYAN); spannableText.setSpan(colorSpan, 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); StyleSpan styleSpan = new StyleSpan(Typeface.BOLD); spannableText.setSpan(styleSpan, 1, 2, Spanned.SPAN_EXCLUSIVE_INCLUSIVE); TypefaceSpan typefaceSpan = new TypefaceSpan("cursive"); spannableText.setSpan(typefaceSpan, 3, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE); ScaleXSpan scaleXSpan = new ScaleXSpan(2.0f); spannableText.setSpan(scaleXSpan, 3, 3, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); mEditText.setText(spannableText); mEditText.setSelection(3); TextAppearanceInfo info = TextAppearanceInfo.createFromTextView(mEditText); // The character before cursor 'b' should only have an AbsoluteSizeSpan. assertEquals(info.getTextSize(), 30f, EPSILON); assertEquals(info.getTextColor(), TEXT_COLOR); assertEquals(info.getTextStyle(), TEXT_STYLE); assertEquals(info.getSystemFontFamilyName(), FONT_FAMILY_NAME); assertEquals(info.getTextScaleX(), TEXT_SCALEX, EPSILON); } @Test public void testCreateFromTextView_contradictorySpans() { // Set multiple contradictory spans AbsoluteSizeSpan sizeSpan1 = new AbsoluteSizeSpan(30); CustomForegroundColorSpan colorSpan1 = new CustomForegroundColorSpan(Color.BLUE); AbsoluteSizeSpan sizeSpan2 = new AbsoluteSizeSpan(10); CustomForegroundColorSpan colorSpan2 = new CustomForegroundColorSpan(Color.GREEN); mSpannableText.setSpan(sizeSpan1, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mSpannableText.setSpan(colorSpan1, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mSpannableText.setSpan(sizeSpan2, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mSpannableText.setSpan(colorSpan2, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mEditText.setText(mSpannableText); mEditText.draw(mCanvas); mEditText.setSelection(3); // Get a copy of the real TextPaint after setting the last span TextPaint realTextPaint = colorSpan2.lastTextPaint; assertNotNull(realTextPaint); TextAppearanceInfo info1 = TextAppearanceInfo.createFromTextView(mEditText); // Verify the real TextPaint equals the last span of multiple contradictory spans assertEquals(info1.getTextSize(), 10f, EPSILON); assertEquals(info1.getTextSize(), realTextPaint.getTextSize(), EPSILON); assertEquals(info1.getTextColor(), Color.GREEN); assertEquals(info1.getTextColor(), realTextPaint.getColor()); assertEquals(info1.getSystemFontFamilyName(), FONT_FAMILY_NAME); } private void assertTextAppearanceInfoContentsEqual(TextAppearanceInfo textAppearanceInfo) { assertEquals(textAppearanceInfo.getTextSize(), TEXT_SIZE, EPSILON); assertEquals(textAppearanceInfo.getTextLocales(), TEXT_LOCALES); assertEquals(textAppearanceInfo.getSystemFontFamilyName(), FONT_FAMILY_NAME); assertEquals(textAppearanceInfo.getTextFontWeight(), TEXT_WEIGHT); assertEquals(textAppearanceInfo.getTextStyle(), TEXT_STYLE); assertEquals(textAppearanceInfo.isAllCaps(), ALL_CAPS); assertEquals(textAppearanceInfo.getShadowRadius(), SHADOW_RADIUS, EPSILON); assertEquals(textAppearanceInfo.getShadowDx(), SHADOW_DX, EPSILON); assertEquals(textAppearanceInfo.getShadowDy(), SHADOW_DY, EPSILON); assertEquals(textAppearanceInfo.getShadowColor(), SHADOW_COLOR); assertEquals(textAppearanceInfo.isElegantTextHeight(), ELEGANT_TEXT_HEIGHT); assertEquals(textAppearanceInfo.isFallbackLineSpacing(), FALLBACK_LINE_SPACING); assertEquals(textAppearanceInfo.getLetterSpacing(), LETTER_SPACING, EPSILON); assertEquals(textAppearanceInfo.getFontFeatureSettings(), FONT_FEATURE_SETTINGS); assertEquals(textAppearanceInfo.getFontVariationSettings(), FONT_VARIATION_SETTINGS); assertEquals(textAppearanceInfo.getLineBreakStyle(), LINE_BREAK_STYLE); assertEquals(textAppearanceInfo.getLineBreakWordStyle(), LINE_BREAK_WORD_STYLE); assertEquals(textAppearanceInfo.getTextScaleX(), TEXT_SCALEX, EPSILON); assertEquals(textAppearanceInfo.getTextColor(), TEXT_COLOR); assertEquals(textAppearanceInfo.getHighlightTextColor(), HIGHLIGHT_TEXT_COLOR); assertEquals(textAppearanceInfo.getHintTextColor(), HINT_TEXT_COLOR); assertEquals(textAppearanceInfo.getLinkTextColor(), LINK_TEXT_COLOR); } static class CustomForegroundColorSpan extends ForegroundColorSpan { @Nullable public TextPaint lastTextPaint = null; CustomForegroundColorSpan(int color) { super(color); } CustomForegroundColorSpan(@NonNull Parcel src) { super(src); } @Override public void updateDrawState(@NonNull TextPaint tp) { super.updateDrawState(tp); // Copy the real TextPaint TextPaint tpCopy = new TextPaint(); tpCopy.set(tp); lastTextPaint = tpCopy; } } }