Merge "Fix minimum line height for TextView use case" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index cc8a213..9c3196d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1804,6 +1804,7 @@
field public static final int useEmbeddedDex = 16844190; // 0x101059e
field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
field public static final int useLevel = 16843167; // 0x101019f
+ field @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public static final int useLocalePreferredLineHeightForMinimum;
field public static final int userVisible = 16843409; // 0x1010291
field public static final int usesCleartextTraffic = 16844012; // 0x10104ec
field public static final int usesPermissionFlags = 16844356; // 0x1010644
@@ -60535,6 +60536,7 @@
method public boolean isFallbackLineSpacing();
method public final boolean isHorizontallyScrollable();
method public boolean isInputMethodTarget();
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public boolean isLocalePreferredLineHeightForMinimumUsed();
method public boolean isSingleLine();
method public boolean isSuggestionsEnabled();
method public boolean isTextSelectable();
@@ -60617,6 +60619,7 @@
method public final void setLinkTextColor(@ColorInt int);
method public final void setLinkTextColor(android.content.res.ColorStateList);
method public final void setLinksClickable(boolean);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void setLocalePreferredLineHeightForMinimumUsed(boolean);
method public void setMarqueeRepeatLimit(int);
method public void setMaxEms(int);
method public void setMaxHeight(int);
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index a6d3bb4..6410609 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -585,9 +585,7 @@
}
if (ClientFlags.fixLineHeightForLocale()) {
- if (minimumFontMetrics == null) {
- paint.getFontMetricsIntForLocale(fm);
- } else {
+ if (minimumFontMetrics != null) {
fm.set(minimumFontMetrics);
// Because the font metrics is provided by public APIs, adjust the top/bottom with
// ascent/descent: top must be smaller than ascent, bottom must be larger than
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 99bd2ff..5986238 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -767,22 +767,14 @@
}
int defaultTop;
- int defaultAscent;
- int defaultDescent;
+ final int defaultAscent;
+ final int defaultDescent;
int defaultBottom;
- if (ClientFlags.fixLineHeightForLocale()) {
- if (b.mMinimumFontMetrics != null) {
- defaultTop = (int) Math.floor(b.mMinimumFontMetrics.top);
- defaultAscent = Math.round(b.mMinimumFontMetrics.ascent);
- defaultDescent = Math.round(b.mMinimumFontMetrics.descent);
- defaultBottom = (int) Math.ceil(b.mMinimumFontMetrics.bottom);
- } else {
- paint.getFontMetricsIntForLocale(fm);
- defaultTop = fm.top;
- defaultAscent = fm.ascent;
- defaultDescent = fm.descent;
- defaultBottom = fm.bottom;
- }
+ if (ClientFlags.fixLineHeightForLocale() && b.mMinimumFontMetrics != null) {
+ defaultTop = (int) Math.floor(b.mMinimumFontMetrics.top);
+ defaultAscent = Math.round(b.mMinimumFontMetrics.ascent);
+ defaultDescent = Math.round(b.mMinimumFontMetrics.descent);
+ defaultBottom = (int) Math.ceil(b.mMinimumFontMetrics.bottom);
// Because the font metrics is provided by public APIs, adjust the top/bottom with
// ascent/descent: top must be smaller than ascent, bottom must be larger than descent.
@@ -1043,10 +1035,10 @@
if (endPos < spanEnd) {
// preserve metrics for current span
- fmTop = fm.top;
- fmBottom = fm.bottom;
- fmAscent = fm.ascent;
- fmDescent = fm.descent;
+ fmTop = Math.min(defaultTop, fm.top);
+ fmBottom = Math.max(defaultBottom, fm.bottom);
+ fmAscent = Math.min(defaultAscent, fm.ascent);
+ fmDescent = Math.max(defaultDescent, fm.descent);
} else {
fmTop = fmBottom = fmAscent = fmDescent = 0;
}
@@ -1069,7 +1061,7 @@
&& mLineCount < mMaximumVisibleLineCount) {
final MeasuredParagraph measuredPara =
MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
- if (ClientFlags.fixLineHeightForLocale()) {
+ if (defaultAscent != 0 && defaultDescent != 0) {
fm.top = defaultTop;
fm.ascent = defaultAscent;
fm.descent = defaultDescent;
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index aa2474d7..3e0161a 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -16,9 +16,13 @@
package android.widget;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.os.Build;
import android.text.Editable;
import android.text.Selection;
import android.text.Spannable;
@@ -29,6 +33,8 @@
import android.util.AttributeSet;
import android.view.KeyEvent;
+import com.android.internal.R;
+
/*
* This is supposed to be a *very* thin veneer over TextView.
* Do not make any changes here that do anything that a TextView
@@ -85,6 +91,11 @@
private static final int ID_ITALIC = android.R.id.italic;
private static final int ID_UNDERLINE = android.R.id.underline;
+ /** @hide */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public static final long LINE_HEIGHT_FOR_LOCALE = 303326708L;
+
public EditText(Context context) {
this(context, null);
}
@@ -104,15 +115,39 @@
final TypedArray a = theme.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.EditText, defStyleAttr, defStyleRes);
- final int n = a.getIndexCount();
- for (int i = 0; i < n; ++i) {
- int attr = a.getIndex(i);
- switch (attr) {
- case com.android.internal.R.styleable.EditText_enableTextStylingShortcuts:
- mStyleShortcutsEnabled = a.getBoolean(attr, false);
- break;
+ try {
+ final int n = a.getIndexCount();
+ for (int i = 0; i < n; ++i) {
+ int attr = a.getIndex(i);
+ switch (attr) {
+ case com.android.internal.R.styleable.EditText_enableTextStylingShortcuts:
+ mStyleShortcutsEnabled = a.getBoolean(attr, false);
+ break;
+ }
}
+ } finally {
+ a.recycle();
}
+
+ boolean hasUseLocalePreferredLineHeightForMinimumInt = false;
+ boolean useLocalePreferredLineHeightForMinimumInt = false;
+ TypedArray tvArray = theme.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
+ try {
+ hasUseLocalePreferredLineHeightForMinimumInt =
+ tvArray.hasValue(R.styleable.TextView_useLocalePreferredLineHeightForMinimum);
+ if (hasUseLocalePreferredLineHeightForMinimumInt) {
+ useLocalePreferredLineHeightForMinimumInt = tvArray.getBoolean(
+ R.styleable.TextView_useLocalePreferredLineHeightForMinimum, false);
+ }
+ } finally {
+ tvArray.recycle();
+ }
+ if (!hasUseLocalePreferredLineHeightForMinimumInt) {
+ useLocalePreferredLineHeightForMinimumInt =
+ CompatChanges.isChangeEnabled(LINE_HEIGHT_FOR_LOCALE);
+ }
+ setLocalePreferredLineHeightForMinimumUsed(useLocalePreferredLineHeightForMinimumInt);
}
@Override
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9a4106d9..57e4e6a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -867,6 +867,8 @@
private boolean mUseBoundsForWidth;
@Nullable private Paint.FontMetrics mMinimumFontMetrics;
+ @Nullable private Paint.FontMetrics mLocalePreferredFontMetrics;
+ private boolean mUseLocalePreferredLineHeightForMinimum;
@ViewDebug.ExportedProperty(category = "text")
@UnsupportedAppUsage
@@ -1617,6 +1619,11 @@
case com.android.internal.R.styleable.TextView_useBoundsForWidth:
mUseBoundsForWidth = a.getBoolean(attr, false);
hasUseBoundForWidthValue = true;
+ break;
+ case com.android.internal.R.styleable
+ .TextView_useLocalePreferredLineHeightForMinimum:
+ mUseLocalePreferredLineHeightForMinimum = a.getBoolean(attr, false);
+ break;
}
}
@@ -4992,6 +4999,41 @@
}
/**
+ * Returns true if the locale preferred line height is used for the minimum line height.
+ *
+ * @return true if using locale preferred line height for the minimum line height. Otherwise
+ * false.
+ *
+ * @see #setLocalePreferredLineHeightForMinimumUsed(boolean)
+ * @see #setMinimumFontMetrics(Paint.FontMetrics)
+ * @see #getMinimumFontMetrics()
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public boolean isLocalePreferredLineHeightForMinimumUsed() {
+ return mUseLocalePreferredLineHeightForMinimum;
+ }
+
+ /**
+ * Set true if the locale preferred line height is used for the minimum line height.
+ *
+ * By setting this flag to true is equivalenet to call
+ * {@link #setMinimumFontMetrics(Paint.FontMetrics)} with the one obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}.
+ *
+ * If custom minimum line height was specified by
+ * {@link #setMinimumFontMetrics(Paint.FontMetrics)}, this flag will be ignored.
+ *
+ * @param flag true for using locale preferred line height for the minimum line height.
+ * @see #isLocalePreferredLineHeightForMinimumUsed()
+ * @see #setMinimumFontMetrics(Paint.FontMetrics)
+ * @see #getMinimumFontMetrics()
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void setLocalePreferredLineHeightForMinimumUsed(boolean flag) {
+ mUseLocalePreferredLineHeightForMinimum = flag;
+ }
+
+ /**
* @return whether fallback line spacing is enabled, {@code true} by default
*
* @see #setFallbackLineSpacing(boolean)
@@ -10728,6 +10770,21 @@
return alignment;
}
+ private Paint.FontMetrics getResolvedMinimumFontMetrics() {
+ if (mMinimumFontMetrics != null) {
+ return mMinimumFontMetrics;
+ }
+ if (!mUseLocalePreferredLineHeightForMinimum) {
+ return null;
+ }
+
+ if (mLocalePreferredFontMetrics == null) {
+ mLocalePreferredFontMetrics = new Paint.FontMetrics();
+ }
+ mTextPaint.getFontMetricsForLocale(mLocalePreferredFontMetrics);
+ return mLocalePreferredFontMetrics;
+ }
+
/**
* The width passed in is now the desired layout width,
* not the full view width with padding.
@@ -10792,7 +10849,8 @@
if (hintBoring == UNKNOWN_BORING) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
isFallbackLineSpacingForBoringLayout(),
- mMinimumFontMetrics, mHintBoring);
+ getResolvedMinimumFontMetrics(), mHintBoring);
+
if (hintBoring != null) {
mHintBoring = hintBoring;
}
@@ -10842,7 +10900,8 @@
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
.setUseBoundsForWidth(mUseBoundsForWidth)
- .setMinimumFontMetrics(mMinimumFontMetrics);
+ .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
+
if (shouldEllipsize) {
builder.setEllipsize(mEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -10907,12 +10966,13 @@
.setUseBoundsForWidth(mUseBoundsForWidth)
.setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
.setEllipsizedWidth(ellipsisWidth)
- .setMinimumFontMetrics(mMinimumFontMetrics);
+ .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
result = builder.build();
} else {
if (boring == UNKNOWN_BORING) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics, mBoring);
+ isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
+ mBoring);
if (boring != null) {
mBoring = boring;
}
@@ -10926,7 +10986,7 @@
wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad, null, wantWidth,
isFallbackLineSpacingForBoringLayout(),
- mUseBoundsForWidth, mMinimumFontMetrics);
+ mUseBoundsForWidth, getResolvedMinimumFontMetrics());
} else {
result = new BoringLayout(
mTransformed,
@@ -10941,7 +11001,7 @@
null,
boring,
mUseBoundsForWidth,
- mMinimumFontMetrics);
+ getResolvedMinimumFontMetrics());
}
if (useSaved) {
@@ -10953,7 +11013,7 @@
wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad, effectiveEllipsize,
ellipsisWidth, isFallbackLineSpacingForBoringLayout(),
- mUseBoundsForWidth, mMinimumFontMetrics);
+ mUseBoundsForWidth, getResolvedMinimumFontMetrics());
} else {
result = new BoringLayout(
mTransformed,
@@ -10968,7 +11028,7 @@
effectiveEllipsize,
boring,
mUseBoundsForWidth,
- mMinimumFontMetrics);
+ getResolvedMinimumFontMetrics());
}
}
}
@@ -10988,7 +11048,7 @@
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
.setUseBoundsForWidth(mUseBoundsForWidth)
- .setMinimumFontMetrics(mMinimumFontMetrics);
+ .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
if (shouldEllipsize) {
builder.setEllipsize(effectiveEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -11116,7 +11176,8 @@
if (des < 0) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics, mBoring);
+ isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
+ mBoring);
if (boring != null) {
mBoring = boring;
}
@@ -11156,7 +11217,7 @@
if (hintDes < 0) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics,
+ isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
@@ -11370,7 +11431,7 @@
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
.setUseBoundsForWidth(mUseBoundsForWidth)
- .setMinimumFontMetrics(mMinimumFontMetrics);
+ .setMinimumFontMetrics(getResolvedMinimumFontMetrics());
final StaticLayout layout = layoutBuilder.build();
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 45861a3..41bc825 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5857,6 +5857,23 @@
use glyph bound's as a source of text width. -->
<!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") -->
<attr name="useBoundsForWidth" format="boolean" />
+ <!-- Whether to use the locale preferred line height for the minimum line height.
+
+ This flag is useful for preventing jitter of entering letters into empty EditText.
+ The line height of the text is determined by the font files used for drawing text in a
+ line. However, in case of the empty text case, the line height cannot be determined and
+ the default line height: usually it is came from a font of Latin script. By making this
+ attribute to true, the TextView/EditText uses a line height that is likely used for the
+ locale associated with the widget. For example, if the system locale is Japanese, the
+ height of the EditText will be adjusted to meet the height of the Japanese font even if
+ the text is empty.
+
+ The default value for EditText is true if targetSdkVersion is
+ {@link android.os.Build.VERSION_CODE#VANILLA_ICE_CREAM} or later, otherwise false.
+ For other TextViews, the default value is false.
+ -->
+ <!-- @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") -->
+ <attr name="useLocalePreferredLineHeightForMinimum" format="boolean" />
</declare-styleable>
<declare-styleable name="TextViewAppearance">
<!-- Base text color, typeface, size, and style. -->
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 0a6779a9..5ee5555 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -153,6 +153,8 @@
<public name="requireContentUriPermissionFromCaller" />
<!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") -->
<public name="languageSettingsActivity"/>
+ <!-- @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") -->
+ <public name="useLocalePreferredLineHeightForMinimum"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">