0

[Font] Try-catch ResourceCompat.getFont

Fallback on using "sans-serif" when font res failed. Add an UMA
"Android.StyleUtils.FontLoadingOutcome" to capture fallback cases.

Bug: 410715278
Change-Id: I97abb867dadfdb5bf081af775a581741e9d89ffe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6496508
Commit-Queue: Wenyu Fu <wenyufu@chromium.org>
Reviewed-by: Sinan Sahin <sinansahin@google.com>
Cr-Commit-Position: refs/heads/main@{#1454035}
This commit is contained in:
Wenyu Fu
2025-04-30 10:31:36 -07:00
committed by Chromium LUCI CQ
parent 3b75e15924
commit 685d64a1f0
4 changed files with 63 additions and 1 deletions
tools/metrics/histograms/metadata/android
ui/android
java
src
org
chromium
junit
src
org
chromium

@ -993,6 +993,12 @@ chromium-metrics-reviews@google.com.
<int value="8" label="Undefined"/>
</enum>
<enum name="FontLoadingOutcome">
<int value="0" label="Fallback Font Family"/>
<int value="1" label="Font resource"/>
<int value="2" label="Font family"/>
</enum>
<enum name="FrameJankStatus">
<int value="0" label="Janky"/>
<int value="1" label="NonJanky"/>

@ -4945,6 +4945,16 @@ chromium-metrics-reviews@google.com.
</summary>
</histogram>
<histogram name="Android.StyleUtils.FontLoadingOutcome"
enum="FontLoadingOutcome" expires_after="2025-11-28">
<owner>wenyufu@chromium.org</owner>
<owner>clank-app-team@google.com</owner>
<summary>
Records the outcome for loading font using applyTextAppearanceToTextPaint.
Recorded during layout initialization on tablets.
</summary>
</histogram>
<histogram name="Android.Survey.DownloadRequested2" enum="BooleanRequested"
expires_after="2025-10-19">
<owner>wenyufu@chromium.org</owner>

@ -5,22 +5,47 @@
package org.chromium.ui.util;
import android.content.Context;
import android.content.res.Resources.NotFoundException;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.text.TextPaint;
import androidx.annotation.IntDef;
import androidx.annotation.StyleRes;
import androidx.annotation.StyleableRes;
import androidx.core.content.res.ResourcesCompat;
import org.chromium.base.Log;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.build.annotations.NullMarked;
import org.chromium.ui.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** Helper functions for working with styles. */
@NullMarked
public class StyleUtils {
private static final String TAG = "StyleUtils";
private static final int INVALID_RESOURCE_ID = -1;
private static final String FALLBACK_FONT_FAMILY_NAME = "sans-serif";
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
@Retention(RetentionPolicy.SOURCE)
@IntDef({
FontLoadingOutcome.FALLBACK_FONT_FAMILY,
FontLoadingOutcome.FONT_RES,
FontLoadingOutcome.FONT_FAMILY,
FontLoadingOutcome.NUM_TOTAL
})
@interface FontLoadingOutcome {
int FALLBACK_FONT_FAMILY = 0;
int FONT_RES = 1;
int FONT_FAMILY = 2;
int NUM_TOTAL = 3;
}
/**
* Applies attributes extracted from a TextAppearance style to a TextPaint object.
@ -45,17 +70,28 @@ public class StyleUtils {
context.getTheme().obtainStyledAttributes(style, R.styleable.TextAppearance);
Typeface typeface;
@FontLoadingOutcome int outcome;
if (applyFontFamily) {
@StyleableRes int fontStyleableRes = R.styleable.TextAppearance_android_fontFamily;
int fontRes = appearance.getResourceId(fontStyleableRes, INVALID_RESOURCE_ID);
if (fontRes != INVALID_RESOURCE_ID) {
typeface = ResourcesCompat.getFont(context, fontRes);
try {
typeface = ResourcesCompat.getFont(context, fontRes);
outcome = FontLoadingOutcome.FONT_RES;
} catch (NotFoundException e) {
Log.e(TAG, "Reading fontRes failed.", e);
typeface = Typeface.create(FALLBACK_FONT_FAMILY_NAME, Typeface.NORMAL);
outcome = FontLoadingOutcome.FALLBACK_FONT_FAMILY;
}
} else {
String fontFamily = appearance.getString(fontStyleableRes);
typeface = Typeface.create(fontFamily, Typeface.NORMAL);
outcome = FontLoadingOutcome.FONT_FAMILY;
}
textPaint.setTypeface(typeface);
RecordHistogram.recordEnumeratedHistogram(
"Android.StyleUtils.FontLoadingOutcome", outcome, FontLoadingOutcome.NUM_TOTAL);
}
if (applyTextSize) {

@ -18,7 +18,9 @@ import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.ui.R;
import org.chromium.ui.util.StyleUtils.FontLoadingOutcome;
/** Tests for {@link StyleUtils} class. */
@RunWith(BaseRobolectricTestRunner.class)
@ -33,6 +35,9 @@ public class StyleUtilsTest {
@Test
public void applyTextAppearanceToTextPaint_StringFontFamily() {
var watcher =
HistogramWatcher.newSingleRecordWatcher(
"Android.StyleUtils.FontLoadingOutcome", FontLoadingOutcome.FONT_FAMILY);
TextPaint textPaint = new TextPaint();
// Should not crash if font-family resource is not found, string font family will be
// constructed instead.
@ -44,10 +49,14 @@ public class StyleUtilsTest {
/* applyFontFamily= */ true,
/* applyTextSize= */ false,
/* applyTextColor= */ false);
watcher.assertExpected();
}
@Test
public void applyTextAppearanceToTextPaint_CustomFontSizeText() {
var watcher =
HistogramWatcher.newSingleRecordWatcher(
"Android.StyleUtils.FontLoadingOutcome", FontLoadingOutcome.FONT_RES);
TextPaint textPaint = new TextPaint();
StyleUtils.applyTextAppearanceToTextPaint(
mContext,
@ -56,6 +65,7 @@ public class StyleUtilsTest {
/* applyFontFamily= */ true,
/* applyTextSize= */ true,
/* applyTextColor= */ true);
watcher.assertExpected();
// Verify test values defined in resources.
assertEquals(
"Applied font is incorrect.",