diff options
| author | 2021-01-17 15:32:01 -0800 | |
|---|---|---|
| committer | 2021-01-19 15:55:41 -0800 | |
| commit | f5859819cdbfe5e3e7f3eefbe4530ca3170f542d (patch) | |
| tree | 0b93f157233f920c67180a99d5f403d11d466a2c | |
| parent | 37fa2b76f1e830ea31f3686d3f8d6df038d134e4 (diff) | |
Refactor font parser for making SystemApi.
This CL breaks system font initialization step into three sub-steps
to be able to expose font configuration as SystemApi. The font
initialization step is now following three steps:
1. Parse XML files. This is SystemFonts#getSystemFontConfig and
SystemFonts#getSystemPreinstalledFontConfig. The first API gives
you the current FontConfig and the second API gives you plain
vanilla FontConfig without any updates.
2. Build font family fallback list from FontConfig. The font family
fallback is a intermediate state that can be used for custom
Typeface. This state is kept for dumping in shell command later.
This is useful for checking what font will be used by the fallback
since family fallback is still printable.
3. Build typeface mapping. The typeface mapping is the final state to
be used by application. This is no longer printable.
Bug: 173619554
Test: atest TypefaceTest TypefaceSystemFallbackTest StaticLayoutTest
Test: atest CtsGraphicsTestCases CtsTextTestCases
Change-Id: I4e21fc7c41f2fb7975d03c1d5f5536a23e8abb09
10 files changed, 715 insertions, 396 deletions
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index 1878d61c78ac..8492363eb503 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -19,171 +19,268 @@ package android.text; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; +import android.graphics.fonts.FontStyle; import android.graphics.fonts.FontVariationAxis; -import android.net.Uri; import android.os.Build; +import android.os.LocaleList; +import java.io.File; import java.lang.annotation.Retention; +import java.util.List; /** * Font configuration descriptions for System fonts. - * @hide + * @hide // TODO Make this SystemApi. */ public final class FontConfig { - private final @NonNull Family[] mFamilies; - private final @NonNull Alias[] mAliases; + private final @NonNull List<Family> mFamilies; + private final @NonNull List<Alias> mAliases; - public FontConfig(@NonNull Family[] families, @NonNull Alias[] aliases) { + /** + * Construct a SystemFontConfig instance. + * + * @param families a list of font families. + * @param aliases a list of aliases. + * + * @hide Only system server can create this instance and passed via IPC. + */ + public FontConfig(@NonNull List<Family> families, @NonNull List<Alias> aliases) { mFamilies = families; mAliases = aliases; } /** * Returns the ordered list of families included in the system fonts. + * + * @return a list of font families. */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public @NonNull Family[] getFamilies() { + public @NonNull List<Family> getFontFamilies() { return mFamilies; } /** * Returns the list of aliases defined for the font families in the system fonts. + * + * @return a list of font families. */ - public @NonNull Alias[] getAliases() { + public @NonNull List<Alias> getAliases() { return mAliases; } /** - * Class that holds information about a Font. + * Returns the ordered list of families included in the system fonts. + * @deprecated Use getFontFamilies instead. + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public @NonNull Family[] getFamilies() { + return mFamilies.toArray(new Family[0]); + } + + /** + * A class represents single font entry in system font configuration. */ public static final class Font { - private final @NonNull String mFontName; - private final int mTtcIndex; - private final @NonNull FontVariationAxis[] mAxes; - private final int mWeight; - private final boolean mIsItalic; - private Uri mUri; - private final String mFallbackFor; + private final @NonNull File mFilePath; + private final @Nullable File mOriginalPath; + private final @NonNull FontStyle mStyle; + private final @IntRange(from = 0) int mIndex; + private final @NonNull String mFontVariationSettings; + private final @Nullable String mFallback; /** - * @hide + * Construct a Font instance. + * + * @hide Only system server can create this instance and passed via IPC. */ - public Font(@NonNull String fontName, int ttcIndex, @NonNull FontVariationAxis[] axes, - int weight, boolean isItalic, String fallbackFor) { - mFontName = fontName; - mTtcIndex = ttcIndex; - mAxes = axes; - mWeight = weight; - mIsItalic = isItalic; - mFallbackFor = fallbackFor; + public Font(@NonNull File filePath, @Nullable File originalPath, @NonNull FontStyle style, + @IntRange(from = 0) int index, @NonNull String fontVariationSettings, + @Nullable String fallback) { + mFilePath = filePath; + mOriginalPath = originalPath; + mStyle = style; + mIndex = index; + mFontVariationSettings = fontVariationSettings; + mFallback = fallback; + } + + /** + * Returns a file to the font file. + * + * @return a font file. + */ + public @NonNull File getFilePath() { + return mFilePath; + } + + /** + * Returns an original font file in the system directory. + * + * If the font file is not updated, returns null. + * + * @return returns the original font file in the system if the font file is updated. Returns + * null if the font file is not updated. + */ + public @Nullable File getOriginalPath() { + return mOriginalPath; + } + + /** + * Returns a font style. + * + * @return a font style. + */ + public @NonNull FontStyle getStyle() { + return mStyle; + } + + /** + * Returns a font index. + * + * @return a font index. + */ + public @IntRange(from = 0) int getIndex() { + return mIndex; } /** - * Returns the name associated by the system to this font. + * Return a font variation settings. + * + * @return a font variation settings. */ - public @NonNull String getFontName() { - return mFontName; + public @NonNull String getFontVariationSettings() { + return mFontVariationSettings; + } + + /** + * Returns font family name that uses this font as a fallback. + * + * If this font is a fallback for the default font family, this is null. + * + * @return a font family name. + */ + public @Nullable String getFallback() { + return mFallback; } /** * Returns the index to be used to access this font when accessing a TTC file. + * @deprecated Use getIndex instead. + * @hide */ + @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public int getTtcIndex() { - return mTtcIndex; + return mIndex; } /** * Returns the list of axes associated to this font. + * @deprecated Use getFontVariationSettings + * @hide */ + @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public @NonNull FontVariationAxis[] getAxes() { - return mAxes; + return FontVariationAxis.fromFontVariationSettings(mFontVariationSettings); } /** * Returns the weight value for this font. + * @deprecated Use getStyle instead. + * @hide */ + @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public int getWeight() { - return mWeight; + return getStyle().getWeight(); } /** * Returns whether this font is italic. + * @deprecated Use getStyle instead. + * @hide */ + @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isItalic() { - return mIsItalic; - } - - /** - * Returns the content uri associated to this font. - * - * You can reach to the font contents by calling {@link - * android.content.ContentResolver#openInputStream}. - */ - public @Nullable Uri getUri() { - return mUri; - } - - public void setUri(@NonNull Uri uri) { - mUri = uri; - } - - public String getFallbackFor() { - return mFallbackFor; + return getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC; } } /** - * Class that holds information about a Font alias. + * A class represents alias between named font families. + * + * In the system font configuration, an font family can be an alias of another font family with + * different font weight. For example, "sans-serif-medium" can be a medium weight of + * sans-serif font family. */ public static final class Alias { - private final @NonNull String mName; - private final @NonNull String mToName; - private final int mWeight; + private final @NonNull String mAliasName; + private final @NonNull String mReferName; + private final @IntRange(from = 0, to = 1000) int mWeight; - public Alias(@NonNull String name, @NonNull String toName, int weight) { - mName = name; - mToName = toName; + /** + * Construct an alias instance. + * + * @param aliasName an alias of the named font family. + * @param referName a referring font family name. + * @param weight a font weight of the referring font family. + * @hide Only system server can create this instance and passed via IPC. + */ + public Alias(@NonNull String aliasName, @NonNull String referName, + @IntRange(from = 0, to = 1000) int weight) { + mAliasName = aliasName; + mReferName = referName; mWeight = weight; } /** - * Returns the new name for the alias. + * An alias of the named font family. + * + * @return an alias of the named font family. */ - public @NonNull String getName() { - return mName; + public @NonNull String getAliasName() { + return mAliasName; } /** - * Returns the existing name to which this alias points to. + * A name of font family referring from {@link #getAliasName()} + * + * @return a referring font family name. */ - public @NonNull String getToName() { - return mToName; + public @NonNull String getReferName() { + return mReferName; } /** - * Returns the weight associated with this alias. + * A font weight of the referring font family. + * + * @return a font weight of the referring font family. */ - public int getWeight() { + public @IntRange(from = 0, to = 1000) int getWeight() { return mWeight; } } /** - * Class that holds information about a Font family. + * A class represents single font family entry in system font configuration. + * + * <p> + * A font family is a bundle of fonts for drawing text in various styles. + * For example, regular style font and bold style font can be bundled into a single font family, + * then system will select the correct style font from family for drawing. */ public static final class Family { - private final @NonNull String mName; - private final @NonNull Font[] mFonts; - // Comma separated BCP47 complient locale strings - private final @NonNull String mLanguages; + private final @NonNull List<Font> mFonts; + private final @Nullable String mName; + private final @Nullable LocaleList mLocaleList; + private final @Variant int mVariant; /** @hide */ @Retention(SOURCE) @@ -212,52 +309,105 @@ public final class FontConfig { /** * Value for font variant. * - * Indiates the font is for elegant variant. + * Indicates the font is for elegant variant. * @see android.graphics.Paint#setElegantTextHeight */ public static final int VARIANT_ELEGANT = 2; - // Must be same with Minikin's variant values. - // See frameworks/minikin/include/minikin/FontFamily.h - private final @Variant int mVariant; - - public Family(@NonNull String name, @NonNull Font[] fonts, @NonNull String languages, - @Variant int variant) { - mName = name; + /** + * Construct a family instance. + * + * @hide Only system server can create this instance and passed via IPC. + */ + public Family(@NonNull List<Font> fonts, @Nullable String name, + @Nullable LocaleList localeList, @Variant int variant) { mFonts = fonts; - mLanguages = languages; + mName = name; + mLocaleList = localeList; mVariant = variant; } /** - * Returns the name given by the system to this font family. + * Returns a list of font files in this family. + * + * @return a list of font files. */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public @Nullable String getName() { + public @NonNull List<Font> getFontList() { + return mFonts; + } + + /** + * Returns a family name if this family defines a new fallback. + * + * @return non-null if a family name is associated. Otherwise null. + */ + public @Nullable String getFallbackName() { return mName; } /** - * Returns the list of fonts included in this family. + * Returns a locale list if associated. + * + * @return non-null if a locale list is associated. Otherwise null. */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public @Nullable Font[] getFonts() { - return mFonts; + public @NonNull LocaleList getLocaleList() { + return mLocaleList; } /** - * Returns the comma separated BCP47 complient languages for this family. May be null. + * Returns a text height variant. + * + * @return text height variant. */ - public @NonNull String getLanguages() { - return mLanguages; + public @Variant int getTextHeightVariant() { + return mVariant; } /** - * Returns the font variant for this family, e.g. "elegant" or "compact". May be null. + * Returns a family variant associated. + * + * @return a family variant. + * @deprecated Use getTextHeightVariant instead. + * @hide */ + @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public @Variant int getVariant() { return mVariant; } + + /** + * Returns a family name if associated. + * + * @return non-null if a family name is associated. Otherwise null. + * @deprecated Use getFallbackName instead. + * @hide + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public @Nullable String getName() { + return mName; + } + + /** + * Returns the list of fonts included in this family. + * @deprecated Use getFontFiles instead + * @hide + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public @Nullable Font[] getFonts() { + return mFonts.toArray(new Font[0]); + } + + /** + * Returns the comma separated BCP47 compliant languages for this family. May be null. + * @deprecated Use getLocaleList instead + * @hide + */ + @Deprecated + public @NonNull String getLanguages() { + return mLocaleList.toLanguageTags(); + } } } diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java index 465ea172b1c0..65ea2a8373aa 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java @@ -21,6 +21,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.res.AssetManager; import android.graphics.fonts.FontCustomizationParser; @@ -44,7 +46,6 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; @@ -139,27 +140,55 @@ public class TypefaceSystemFallbackTest { } } - private static void buildSystemFallback(String xml, - FontCustomizationParser.Result oemCustomization, ArrayMap<String, Typeface> fontMap, - ArrayMap<String, FontFamily[]> fallbackMap) { + private static void buildSystemFallback( + @NonNull String xml, + @Nullable String oemXml, + @NonNull ArrayMap<String, Typeface> outFontMap, + @NonNull ArrayMap<String, FontFamily[]> outFallbackMap) { try (FileOutputStream fos = new FileOutputStream(TEST_FONTS_XML)) { - fos.write(xml.getBytes(Charset.forName("UTF-8"))); + fos.write(xml.getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { throw new RuntimeException(e); } + + String oemXmlPath; + if (oemXml != null) { + try (FileOutputStream fos = new FileOutputStream(TEST_OEM_XML)) { + fos.write(oemXml.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException(e); + } + oemXmlPath = TEST_OEM_XML; + } else { + oemXmlPath = null; + } + Map<String, File> updatableFontMap = new HashMap<>(); for (File file : new File(TEST_UPDATABLE_FONT_DIR).listFiles()) { updatableFontMap.put(file.getName(), file); } - final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(TEST_FONTS_XML, - TEST_FONT_DIR, updatableFontMap, oemCustomization, fallbackMap); - Typeface.initSystemDefaultTypefaces(fontMap, fallbackMap, aliases); + FontConfig fontConfig; + try { + fontConfig = FontListParser.parse( + TEST_FONTS_XML, TEST_FONT_DIR, oemXmlPath, TEST_OEM_DIR, updatableFontMap); + } catch (IOException | XmlPullParserException e) { + throw new RuntimeException(e); + } + + Map<String, FontFamily[]> fallbackMap = SystemFonts.buildSystemFallback(fontConfig); + Map<String, Typeface> typefaceMap = SystemFonts.buildSystemTypefaces( + fontConfig, fallbackMap); + + outFontMap.clear(); + outFontMap.putAll(typefaceMap); + outFallbackMap.clear(); + outFallbackMap.putAll(fallbackMap); } private static FontCustomizationParser.Result readFontCustomization(String oemXml) { try (InputStream is = new ByteArrayInputStream(oemXml.getBytes(StandardCharsets.UTF_8))) { - return FontCustomizationParser.parse(is, TEST_OEM_DIR); + return FontCustomizationParser.parse(is, TEST_OEM_DIR, null); } catch (IOException | XmlPullParserException e) { throw new RuntimeException(e); } @@ -167,19 +196,22 @@ public class TypefaceSystemFallbackTest { @Test public void testBuildSystemFallback() { - final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); - final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - new FontCustomizationParser.Result(); - - final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(SYSTEM_FONTS_XML, - SYSTEM_FONT_DIR, oemCustomization, fallbackMap); + FontConfig fontConfig; + try { + fontConfig = FontListParser.parse( + SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, null, TEST_OEM_DIR, null); + } catch (IOException | XmlPullParserException e) { + throw new RuntimeException(e); + } + assertFalse(fontConfig.getAliases().isEmpty()); + assertFalse(fontConfig.getFontFamilies().isEmpty()); - assertNotNull(aliases); + Map<String, FontFamily[]> fallbackMap = SystemFonts.buildSystemFallback(fontConfig); assertFalse(fallbackMap.isEmpty()); - Typeface.initSystemDefaultTypefaces(fontMap, fallbackMap, aliases); - assertFalse(fontMap.isEmpty()); + Map<String, Typeface> typefaceMap = SystemFonts.buildSystemTypefaces( + fontConfig, fallbackMap); + assertFalse(typefaceMap.isEmpty()); } @Test @@ -199,10 +231,8 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - new FontCustomizationParser.Result(); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, null, fontMap, fallbackMap); assertEquals(1, fontMap.size()); assertTrue(fontMap.containsKey("sans-serif")); @@ -229,10 +259,8 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - new FontCustomizationParser.Result(); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, null, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -277,10 +305,8 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - new FontCustomizationParser.Result(); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, null, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -324,10 +350,8 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - new FontCustomizationParser.Result(); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, null, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -376,10 +400,8 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - new FontCustomizationParser.Result(); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, null, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -424,10 +446,8 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - new FontCustomizationParser.Result(); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, null, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -465,10 +485,8 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - new FontCustomizationParser.Result(); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, null, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -506,10 +524,8 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - new FontCustomizationParser.Result(); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, null, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -556,10 +572,8 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - new FontCustomizationParser.Result(); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, null, fontMap, fallbackMap); final Paint paint = new Paint(); paint.setTypeface(fontMap.get("sans-serif")); @@ -600,10 +614,8 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - new FontCustomizationParser.Result(); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, null, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -641,10 +653,8 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - new FontCustomizationParser.Result(); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, null, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -679,10 +689,8 @@ public class TypefaceSystemFallbackTest { + "</fonts-modification>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - readFontCustomization(oemXml); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, oemXml, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -717,10 +725,8 @@ public class TypefaceSystemFallbackTest { + "</fonts-modification>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - readFontCustomization(oemXml); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, oemXml, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -751,10 +757,8 @@ public class TypefaceSystemFallbackTest { + "</fonts-modification>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - readFontCustomization(oemXml); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, oemXml, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -804,10 +808,8 @@ public class TypefaceSystemFallbackTest { + "</fonts-modification>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - readFontCustomization(oemXml); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, oemXml, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -862,12 +864,10 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - new FontCustomizationParser.Result(); // Install all2em.ttf as a3em.ttf copyAssetToFile("fonts/all2em.ttf", new File(TEST_UPDATABLE_FONT_DIR, "a3em.ttf")); - buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + buildSystemFallback(xml, null, fontMap, fallbackMap); final Paint paint = new Paint(); diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java index 392c6b7199a5..d12f495055e1 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java @@ -27,7 +27,6 @@ import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; import android.os.SharedMemory; import android.text.FontConfig; -import android.util.Pair; import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; @@ -41,7 +40,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.nio.ByteOrder; -import java.util.HashMap; import java.util.Map; import java.util.Random; @@ -197,10 +195,10 @@ public class TypefaceTest { @SmallTest @Test public void testSerialize() throws Exception { - HashMap<String, Typeface> systemFontMap = new HashMap<>(); - Pair<FontConfig.Alias[], Map<String, FontFamily[]>> res = - SystemFonts.initializePreinstalledFonts(); - Typeface.initSystemDefaultTypefaces(systemFontMap, res.second, res.first); + FontConfig fontConfig = SystemFonts.getSystemPreinstalledFontConfig(); + Map<String, FontFamily[]> fallbackMap = SystemFonts.buildSystemFallback(fontConfig); + Map<String, Typeface> systemFontMap = SystemFonts.buildSystemTypefaces(fontConfig, + fallbackMap); SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap); Map<String, Typeface> copiedFontMap = Typeface.deserializeFontMap(sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN)); diff --git a/core/tests/coretests/src/android/text/FontFallbackSetup.java b/core/tests/coretests/src/android/text/FontFallbackSetup.java index 64e6f82d82af..e301037d01f1 100644 --- a/core/tests/coretests/src/android/text/FontFallbackSetup.java +++ b/core/tests/coretests/src/android/text/FontFallbackSetup.java @@ -19,14 +19,15 @@ package android.text; import android.annotation.NonNull; import android.content.Context; import android.content.res.AssetManager; +import android.graphics.FontListParser; import android.graphics.Typeface; -import android.graphics.fonts.FontCustomizationParser; import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; -import android.util.ArrayMap; import androidx.test.InstrumentationRegistry; +import org.xmlpull.v1.XmlPullParserException; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -34,12 +35,13 @@ import java.io.InputStream; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.StandardCopyOption; +import java.util.Map; public class FontFallbackSetup implements AutoCloseable { private final String[] mTestFontFiles; private final String mXml; private final String mTestFontsDir; - final ArrayMap<String, Typeface> mFontMap = new ArrayMap<>(); + private final Map<String, Typeface> mFontMap; public FontFallbackSetup(@NonNull String testSubDir, @NonNull String[] testFontFiles, @NonNull String xml) { @@ -75,12 +77,15 @@ public class FontFallbackSetup implements AutoCloseable { throw new RuntimeException(e); } - final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final FontCustomizationParser.Result oemCustomization = - new FontCustomizationParser.Result(); - final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(testFontsXml, - mTestFontsDir, oemCustomization, fallbackMap); - Typeface.initSystemDefaultTypefaces(mFontMap, fallbackMap, aliases); + FontConfig fontConfig; + try { + fontConfig = FontListParser.parse(testFontsXml, mTestFontsDir, null, null, null); + } catch (IOException | XmlPullParserException e) { + throw new RuntimeException(e); + } + + Map<String, FontFamily[]> fallbackMap = SystemFonts.buildSystemFallback(fontConfig); + mFontMap = SystemFonts.buildSystemTypefaces(fontConfig, fallbackMap); } @NonNull diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index af100c96f6f5..ff381176e38d 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -16,10 +16,14 @@ package android.graphics; +import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; +import android.graphics.fonts.FontCustomizationParser; +import android.graphics.fonts.FontStyle; import android.graphics.fonts.FontVariationAxis; import android.os.Build; +import android.os.LocaleList; import android.text.FontConfig; import android.util.Xml; @@ -27,6 +31,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -36,7 +41,6 @@ import java.util.regex.Pattern; /** * Parser for font config files. - * * @hide */ public class FontListParser { @@ -44,49 +48,89 @@ public class FontListParser { /* Parse fallback list (no names) */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException { - return parse(in, "/system/fonts", null); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parser.nextTag(); + return readFamilies(parser, "/system/fonts/", new FontCustomizationParser.Result(), null); } /** - * Parse the fonts.xml + * Parses system font config XMLs + * + * @param fontsXmlPath location of fonts.xml + * @param systemFontDir location of system font directory + * @param oemCustomizationXmlPath location of oem_customization.xml + * @param productFontDir location of oem customized font directory + * @param updatableFontMap map of updated font files. + * @return font configuration + * @throws IOException + * @throws XmlPullParserException */ - public static FontConfig parse(InputStream in, String fontDir, - @Nullable Map<String, File> updatableFontMap) - throws XmlPullParserException, IOException { - try { + public static FontConfig parse( + @NonNull String fontsXmlPath, + @NonNull String systemFontDir, + @Nullable String oemCustomizationXmlPath, + @Nullable String productFontDir, + @Nullable Map<String, File> updatableFontMap + ) throws IOException, XmlPullParserException { + FontCustomizationParser.Result oemCustomization; + if (oemCustomizationXmlPath != null) { + try (InputStream is = new FileInputStream(oemCustomizationXmlPath)) { + oemCustomization = FontCustomizationParser.parse(is, productFontDir, + updatableFontMap); + } catch (IOException e) { + // OEM customization may not exists. Ignoring + oemCustomization = new FontCustomizationParser.Result(); + } + } else { + oemCustomization = new FontCustomizationParser.Result(); + } + + try (InputStream is = new FileInputStream(fontsXmlPath)) { XmlPullParser parser = Xml.newPullParser(); - parser.setInput(in, null); + parser.setInput(is, null); parser.nextTag(); - return readFamilies(parser, fontDir, updatableFontMap); - } finally { - in.close(); + return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap); } } - private static FontConfig readFamilies(XmlPullParser parser, String fontDir, + private static FontConfig readFamilies( + @NonNull XmlPullParser parser, + @NonNull String fontDir, + @NonNull FontCustomizationParser.Result customization, @Nullable Map<String, File> updatableFontMap) throws XmlPullParserException, IOException { List<FontConfig.Family> families = new ArrayList<>(); - List<FontConfig.Alias> aliases = new ArrayList<>(); + List<FontConfig.Alias> aliases = new ArrayList<>(customization.getAdditionalAliases()); + + Map<String, FontConfig.Family> oemNamedFamilies = + customization.getAdditionalNamedFamilies(); parser.require(XmlPullParser.START_TAG, null, "familyset"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("family")) { - families.add(readFamily(parser, fontDir, updatableFontMap)); + FontConfig.Family family = readFamily(parser, fontDir, updatableFontMap); + String name = family.getFallbackName(); + if (name == null || !oemNamedFamilies.containsKey(name)) { + // The OEM customization overrides system named family. Skip if OEM + // customization XML defines the same named family. + families.add(family); + } } else if (tag.equals("alias")) { aliases.add(readAlias(parser)); } else { skip(parser); } } - return new FontConfig(families.toArray(new FontConfig.Family[families.size()]), - aliases.toArray(new FontConfig.Alias[aliases.size()])); + + families.addAll(oemNamedFamilies.values()); + return new FontConfig(families, aliases); } /** - * Reads a family element + * Read family tag in fonts.xml or oem_customization.xml */ public static FontConfig.Family readFamily(XmlPullParser parser, String fontDir, @Nullable Map<String, File> updatableFontMap) @@ -94,7 +138,7 @@ public class FontListParser { final String name = parser.getAttributeValue(null, "name"); final String lang = parser.getAttributeValue("", "lang"); final String variant = parser.getAttributeValue(null, "variant"); - final List<FontConfig.Font> fonts = new ArrayList<FontConfig.Font>(); + final List<FontConfig.Font> fonts = new ArrayList<>(); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; final String tag = parser.getName(); @@ -112,20 +156,22 @@ public class FontListParser { intVariant = FontConfig.Family.VARIANT_ELEGANT; } } - return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), lang, - intVariant); + return new FontConfig.Family(fonts, name, LocaleList.forLanguageTags(lang), intVariant); } /** Matches leading and trailing XML whitespace. */ private static final Pattern FILENAME_WHITESPACE_PATTERN = Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$"); - private static FontConfig.Font readFont(XmlPullParser parser, String fontDir, + private static FontConfig.Font readFont( + @NonNull XmlPullParser parser, + @NonNull String fontDir, @Nullable Map<String, File> updatableFontMap) throws XmlPullParserException, IOException { + String indexStr = parser.getAttributeValue(null, "index"); int index = indexStr == null ? 0 : Integer.parseInt(indexStr); - List<FontVariationAxis> axes = new ArrayList<FontVariationAxis>(); + List<FontVariationAxis> axes = new ArrayList<>(); String weightStr = parser.getAttributeValue(null, "weight"); int weight = weightStr == null ? 400 : Integer.parseInt(weightStr); boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style")); @@ -144,12 +190,37 @@ public class FontListParser { } } String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll(""); - String fontName = findFontFile(sanitizedName, fontDir, updatableFontMap); - return new FontConfig.Font(fontName, index, axes.toArray( - new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor); + String updatedName = findUpdatedFontFile(sanitizedName, updatableFontMap); + String filePath; + String originalPath; + if (updatedName != null) { + filePath = updatedName; + originalPath = fontDir + sanitizedName; + } else { + filePath = fontDir + sanitizedName; + originalPath = null; + } + + String varSettings; + if (axes.isEmpty()) { + varSettings = ""; + } else { + varSettings = FontVariationAxis.toFontVariationSettings( + axes.toArray(new FontVariationAxis[0])); + } + + return new FontConfig.Font(new File(filePath), + originalPath == null ? null : new File(originalPath), + new FontStyle( + weight, + isItalic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT + ), + index, + varSettings, + fallbackFor); } - private static String findFontFile(String name, String fontDir, + private static String findUpdatedFontFile(String name, @Nullable Map<String, File> updatableFontMap) { if (updatableFontMap != null) { File updatedFile = updatableFontMap.get(name); @@ -157,7 +228,7 @@ public class FontListParser { return updatedFile.getAbsolutePath(); } } - return fontDir + name; + return null; } private static FontVariationAxis readAxis(XmlPullParser parser) @@ -193,12 +264,12 @@ public class FontListParser { int depth = 1; while (depth > 0) { switch (parser.next()) { - case XmlPullParser.START_TAG: - depth++; - break; - case XmlPullParser.END_TAG: - depth--; - break; + case XmlPullParser.START_TAG: + depth++; + break; + case XmlPullParser.END_TAG: + depth--; + break; } } } diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 48b474d6322e..f1866cdceae7 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -45,7 +45,6 @@ import android.text.FontConfig; import android.util.Base64; import android.util.LongSparseArray; import android.util.LruCache; -import android.util.Pair; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -197,7 +196,11 @@ public class Typeface { // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp /** @hide */ public static final int RESOLVE_BY_FONT_TABLE = -1; - private static final String DEFAULT_FAMILY = "sans-serif"; + /** + * The key of the default font family. + * @hide + */ + public static final String DEFAULT_FAMILY = "sans-serif"; // Style value for building typeface. private static final int STYLE_NORMAL = 0; @@ -1139,18 +1142,19 @@ public class Typeface { /** @hide */ @VisibleForTesting - public static void initSystemDefaultTypefaces(Map<String, Typeface> systemFontMap, - Map<String, FontFamily[]> fallbacks, - FontConfig.Alias[] aliases) { + public static void initSystemDefaultTypefaces(Map<String, FontFamily[]> fallbacks, + List<FontConfig.Alias> aliases, + Map<String, Typeface> outSystemFontMap) { for (Map.Entry<String, FontFamily[]> entry : fallbacks.entrySet()) { - systemFontMap.put(entry.getKey(), createFromFamilies(entry.getValue())); + outSystemFontMap.put(entry.getKey(), createFromFamilies(entry.getValue())); } - for (FontConfig.Alias alias : aliases) { - if (systemFontMap.containsKey(alias.getName())) { + for (int i = 0; i < aliases.size(); ++i) { + final FontConfig.Alias alias = aliases.get(i); + if (outSystemFontMap.containsKey(alias.getAliasName())) { continue; // If alias and named family are conflict, use named family. } - final Typeface base = systemFontMap.get(alias.getToName()); + final Typeface base = outSystemFontMap.get(alias.getReferName()); if (base == null) { // The missing target is a valid thing, some configuration don't have font files, // e.g. wear devices. Just skip this alias. @@ -1159,7 +1163,7 @@ public class Typeface { final int weight = alias.getWeight(); final Typeface newFace = weight == 400 ? base : new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); - systemFontMap.put(alias.getName(), newFace); + outSystemFontMap.put(alias.getAliasName(), newFace); } } @@ -1339,11 +1343,11 @@ public class Typeface { /** @hide */ public static void loadPreinstalledSystemFontMap() { - final HashMap<String, Typeface> systemFontMap = new HashMap<>(); - Pair<FontConfig.Alias[], Map<String, FontFamily[]>> pair = - SystemFonts.initializePreinstalledFonts(); - initSystemDefaultTypefaces(systemFontMap, pair.second, pair.first); - setSystemFontMap(systemFontMap); + final FontConfig fontConfig = SystemFonts.getSystemPreinstalledFontConfig(); + final Map<String, FontFamily[]> fallback = SystemFonts.buildSystemFallback(fontConfig); + final Map<String, Typeface> typefaceMap = + SystemFonts.buildSystemTypefaces(fontConfig, fallback); + setSystemFontMap(typefaceMap); } static { diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java index f95da82ee07c..1ad6fbe264cb 100644 --- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java +++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java @@ -16,18 +16,25 @@ package android.graphics.fonts; +import static android.text.FontConfig.Alias; +import static android.text.FontConfig.Family; + import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.FontListParser; -import android.text.FontConfig; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.HashSet; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Parser for font customization @@ -39,8 +46,27 @@ public class FontCustomizationParser { * Represents a customization XML */ public static class Result { - ArrayList<FontConfig.Family> mAdditionalNamedFamilies = new ArrayList<>(); - ArrayList<FontConfig.Alias> mAdditionalAliases = new ArrayList<>(); + private final Map<String, Family> mAdditionalNamedFamilies; + private final List<Alias> mAdditionalAliases; + + public Result() { + mAdditionalNamedFamilies = Collections.emptyMap(); + mAdditionalAliases = Collections.emptyList(); + } + + public Result(Map<String, Family> additionalNamedFamilies, + List<Alias> additionalAliases) { + mAdditionalNamedFamilies = additionalNamedFamilies; + mAdditionalAliases = additionalAliases; + } + + public Map<String, Family> getAdditionalNamedFamilies() { + return mAdditionalNamedFamilies; + } + + public List<Alias> getAdditionalAliases() { + return mAdditionalAliases; + } } /** @@ -48,56 +74,67 @@ public class FontCustomizationParser { * * Caller must close the input stream */ - public static Result parse(@NonNull InputStream in, @NonNull String fontDir) - throws XmlPullParserException, IOException { + public static Result parse( + @NonNull InputStream in, + @NonNull String fontDir, + @Nullable Map<String, File> updatableFontMap + ) throws XmlPullParserException, IOException { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, null); parser.nextTag(); - return readFamilies(parser, fontDir); + return readFamilies(parser, fontDir, updatableFontMap); } - private static void validate(Result result) { - HashSet<String> familyNames = new HashSet<>(); - for (int i = 0; i < result.mAdditionalNamedFamilies.size(); ++i) { - final FontConfig.Family family = result.mAdditionalNamedFamilies.get(i); - final String name = family.getName(); + private static Map<String, Family> validateAndTransformToMap(List<Family> families) { + HashMap<String, Family> namedFamily = new HashMap<>(); + for (int i = 0; i < families.size(); ++i) { + final Family family = families.get(i); + final String name = family.getFallbackName(); if (name == null) { throw new IllegalArgumentException("new-named-family requires name attribute"); } - if (!familyNames.add(name)) { + if (namedFamily.put(name, family) != null) { throw new IllegalArgumentException( "new-named-family requires unique name attribute"); } } + return namedFamily; } - private static Result readFamilies(XmlPullParser parser, String fontDir) - throws XmlPullParserException, IOException { - Result out = new Result(); + private static Result readFamilies( + @NonNull XmlPullParser parser, + @NonNull String fontDir, + @Nullable Map<String, File> updatableFontMap + ) throws XmlPullParserException, IOException { + List<Family> families = new ArrayList<>(); + List<Alias> aliases = new ArrayList<>(); parser.require(XmlPullParser.START_TAG, null, "fonts-modification"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("family")) { - readFamily(parser, fontDir, out); + readFamily(parser, fontDir, families, updatableFontMap); } else if (tag.equals("alias")) { - out.mAdditionalAliases.add(FontListParser.readAlias(parser)); + aliases.add(FontListParser.readAlias(parser)); } else { FontListParser.skip(parser); } } - validate(out); - return out; + return new Result(validateAndTransformToMap(families), aliases); } - private static void readFamily(XmlPullParser parser, String fontDir, Result out) + private static void readFamily( + @NonNull XmlPullParser parser, + @NonNull String fontDir, + @NonNull List<Family> out, + @Nullable Map<String, File> updatableFontMap) throws XmlPullParserException, IOException { final String customizationType = parser.getAttributeValue(null, "customizationType"); if (customizationType == null) { throw new IllegalArgumentException("customizationType must be specified"); } if (customizationType.equals("new-named-family")) { - out.mAdditionalNamedFamilies.add(FontListParser.readFamily(parser, fontDir, null)); + out.add(FontListParser.readFamily(parser, fontDir, updatableFontMap)); } else { throw new IllegalArgumentException("Unknown customizationType=" + customizationType); } diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java index 75ea12062929..2f0c26f06df7 100644 --- a/graphics/java/android/graphics/fonts/FontFamily.java +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -18,6 +18,7 @@ package android.graphics.fonts; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; import android.text.FontConfig; import com.android.internal.util.Preconditions; @@ -119,7 +120,7 @@ public final class FontFamily { nAddFont(builderPtr, mFonts.get(i).getNativePtr()); } final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback); - final FontFamily family = new FontFamily(mFonts, ptr); + final FontFamily family = new FontFamily(mFonts, langTags, variant, ptr); sFamilyRegistory.registerNativeAllocation(family, ptr); return family; } @@ -138,15 +139,36 @@ public final class FontFamily { } private final ArrayList<Font> mFonts; + private final String mLangTags; + private final int mVariant; private final long mNativePtr; // Use Builder instead. - private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) { + private FontFamily(@NonNull ArrayList<Font> fonts, String langTags, int variant, long ptr) { mFonts = fonts; + mLangTags = langTags; + mVariant = variant; mNativePtr = ptr; } /** + * Returns a BCP-47 compliant language tags associated with this font family. + * @hide + * @return a BCP-47 compliant language tag. + */ + public @Nullable String getLangTags() { + return mLangTags; + } + + /** + * @hide + * @return a family variant + */ + public int getVariant() { + return mVariant; + } + + /** * Returns a font * * @param index an index of the font diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index 93b1fcc3ba1f..54167b4b3042 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -23,11 +23,9 @@ import android.graphics.Typeface; import android.text.FontConfig; import android.util.ArrayMap; import android.util.Log; -import android.util.Pair; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import org.xmlpull.v1.XmlPullParserException; @@ -37,7 +35,6 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -52,6 +49,11 @@ public final class SystemFonts { private static final String TAG = "SystemFonts"; private static final String DEFAULT_FAMILY = "sans-serif"; + private static final String FONTS_XML = "/system/etc/fonts.xml"; + private static final String SYSTEM_FONT_DIR = "/system/fonts/"; + private static final String OEM_XML = "/product/etc/fonts_customization.xml"; + private static final String OEM_FONT_DIR = "/product/fonts/"; + private SystemFonts() {} // Do not instansiate. private static final Object LOCK = new Object(); @@ -88,12 +90,10 @@ public final class SystemFonts { } private static @NonNull Set<Font> collectAllFonts() { - final FontCustomizationParser.Result oemCustomization = - readFontCustomization("/product/etc/fonts_customization.xml", "/product/fonts/"); - Map<String, FontFamily[]> map = new ArrayMap<>(); // TODO: use updated fonts - buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", null /* updatableFontMap */, - oemCustomization, map); + FontConfig fontConfig = getSystemPreinstalledFontConfig(); + Map<String, FontFamily[]> map = buildSystemFallback(fontConfig); + Set<Font> res = new HashSet<>(); for (FontFamily[] families : map.values()) { for (FontFamily family : families) { @@ -119,15 +119,16 @@ public final class SystemFonts { @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackMap, @NonNull Map<String, ByteBuffer> cache) { - final String languageTags = xmlFamily.getLanguages(); - final int variant = xmlFamily.getVariant(); + final String languageTags = xmlFamily.getLocaleList().toLanguageTags(); + final int variant = xmlFamily.getTextHeightVariant(); final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>(); - final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = new ArrayMap<>(); + final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = + new ArrayMap<>(); // Collect default fallback and specific fallback fonts. for (final FontConfig.Font font : xmlFamily.getFonts()) { - final String fallbackName = font.getFallbackFor(); + final String fallbackName = font.getFallback(); if (fallbackName == null) { defaultFonts.add(font); } else { @@ -141,19 +142,22 @@ public final class SystemFonts { } final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily( - xmlFamily.getName(), defaultFonts, languageTags, variant, cache); + xmlFamily.getFallbackName(), defaultFonts, languageTags, variant, cache); // Insert family into fallback map. for (int i = 0; i < fallbackMap.size(); i++) { - final ArrayList<FontConfig.Font> fallback = - specificFallbackFonts.get(fallbackMap.keyAt(i)); + String name = fallbackMap.keyAt(i); + final ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(name); if (fallback == null) { - if (defaultFamily != null) { + String familyName = xmlFamily.getFallbackName(); + if (defaultFamily != null + // do not add myself to the fallback chain. + && (familyName == null || !familyName.equals(name))) { fallbackMap.valueAt(i).add(defaultFamily); } } else { final FontFamily family = createFontFamily( - xmlFamily.getName(), fallback, languageTags, variant, cache); + xmlFamily.getFallbackName(), fallback, languageTags, variant, cache); if (family != null) { fallbackMap.valueAt(i).add(family); } else if (defaultFamily != null) { @@ -177,7 +181,7 @@ public final class SystemFonts { FontFamily.Builder b = null; for (int i = 0; i < fonts.size(); i++) { final FontConfig.Font fontConfig = fonts.get(i); - final String fullPath = fontConfig.getFontName(); + final String fullPath = fontConfig.getFilePath().getAbsolutePath(); ByteBuffer buffer = cache.get(fullPath); if (buffer == null) { if (cache.containsKey(fullPath)) { @@ -193,11 +197,10 @@ public final class SystemFonts { final Font font; try { font = new Font.Builder(buffer, new File(fullPath), languageTags) - .setWeight(fontConfig.getWeight()) - .setSlant(fontConfig.isItalic() ? FontStyle.FONT_SLANT_ITALIC - : FontStyle.FONT_SLANT_UPRIGHT) - .setTtcIndex(fontConfig.getTtcIndex()) - .setFontVariationSettings(fontConfig.getAxes()) + .setWeight(fontConfig.getStyle().getWeight()) + .setSlant(fontConfig.getStyle().getSlant()) + .setTtcIndex(fontConfig.getIndex()) + .setFontVariationSettings(fontConfig.getFontVariationSettings()) .build(); } catch (IOException e) { throw new RuntimeException(e); // Never reaches here @@ -215,10 +218,11 @@ public final class SystemFonts { private static void appendNamedFamily(@NonNull FontConfig.Family xmlFamily, @NonNull HashMap<String, ByteBuffer> bufferCache, @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackListMap) { - final String familyName = xmlFamily.getName(); + final String familyName = xmlFamily.getFallbackName(); final FontFamily family = createFontFamily( - familyName, Arrays.asList(xmlFamily.getFonts()), - xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache); + familyName, xmlFamily.getFontList(), + xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getTextHeightVariant(), + bufferCache); if (family == null) { return; } @@ -228,115 +232,104 @@ public final class SystemFonts { } /** - * @see #buildSystemFallback(String, String, Map, FontCustomizationParser.Result, Map) + * Get the updated FontConfig. + * + * @param updatableFontMap a font mapping of updated font files. * @hide */ - @VisibleForTesting - public static FontConfig.Alias[] buildSystemFallback(@NonNull String xmlPath, - @NonNull String fontDir, - @NonNull FontCustomizationParser.Result oemCustomization, - @NonNull Map<String, FontFamily[]> fallbackMap) { - return buildSystemFallback(xmlPath, fontDir, null /* updatableFontMap */, - oemCustomization, fallbackMap); + public static @NonNull FontConfig getSystemFontConfig( + @Nullable Map<String, File> updatableFontMap + ) { + return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, + updatableFontMap); } /** - * Build the system fallback from xml file. - * - * @param xmlPath A full path string to the fonts.xml file. - * @param fontDir A full path string to the system font directory. This must end with - * slash('/'). - * @param updatableFontMap A map from font file name to updated font file path. - * @param fallbackMap An output system fallback map. Caller must pass empty map. - * @return a list of aliases + * Get the system preinstalled FontConfig. * @hide */ - @VisibleForTesting - public static FontConfig.Alias[] buildSystemFallback(@NonNull String xmlPath, - @NonNull String fontDir, - @Nullable Map<String, File> updatableFontMap, - @NonNull FontCustomizationParser.Result oemCustomization, - @NonNull Map<String, FontFamily[]> fallbackMap) { - try { - final FileInputStream fontsIn = new FileInputStream(xmlPath); - final FontConfig fontConfig = FontListParser.parse(fontsIn, fontDir, updatableFontMap); - - final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>(); - final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies(); - - final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>(); - // First traverse families which have a 'name' attribute to create fallback map. - for (final FontConfig.Family xmlFamily : xmlFamilies) { - final String familyName = xmlFamily.getName(); - if (familyName == null) { - continue; - } - appendNamedFamily(xmlFamily, bufferCache, fallbackListMap); - } + public static @NonNull FontConfig getSystemPreinstalledFontConfig() { + return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null); + } - for (int i = 0; i < oemCustomization.mAdditionalNamedFamilies.size(); ++i) { - appendNamedFamily(oemCustomization.mAdditionalNamedFamilies.get(i), - bufferCache, fallbackListMap); - } + /* package */ static @NonNull FontConfig getSystemFontConfigInternal( + @NonNull String fontsXml, + @NonNull String systemFontDir, + @Nullable String oemXml, + @Nullable String productFontDir, + @Nullable Map<String, File> updatableFontMap + ) { + try { + return FontListParser.parse(fontsXml, systemFontDir, oemXml, productFontDir, + updatableFontMap); + } catch (IOException e) { + Log.e(TAG, "Failed to open/read system font configurations.", e); + return new FontConfig(Collections.emptyList(), Collections.emptyList()); + } catch (XmlPullParserException e) { + Log.e(TAG, "Failed to parse the system font configuration.", e); + return new FontConfig(Collections.emptyList(), Collections.emptyList()); + } + } - // Then, add fallback fonts to the each fallback map. - for (int i = 0; i < xmlFamilies.length; i++) { - final FontConfig.Family xmlFamily = xmlFamilies[i]; - // The first family (usually the sans-serif family) is always placed immediately - // after the primary family in the fallback. - if (i == 0 || xmlFamily.getName() == null) { - pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache); - } + /** + * Build the system fallback from FontConfig. + * @hide + */ + @VisibleForTesting + public static Map<String, FontFamily[]> buildSystemFallback(FontConfig fontConfig) { + final Map<String, FontFamily[]> fallbackMap = new HashMap<>(); + final HashMap<String, ByteBuffer> bufferCache = new HashMap<>(); + final List<FontConfig.Family> xmlFamilies = fontConfig.getFontFamilies(); + + final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>(); + // First traverse families which have a 'name' attribute to create fallback map. + for (final FontConfig.Family xmlFamily : xmlFamilies) { + final String familyName = xmlFamily.getFallbackName(); + if (familyName == null) { + continue; } + appendNamedFamily(xmlFamily, bufferCache, fallbackListMap); + } - // Build the font map and fallback map. - for (int i = 0; i < fallbackListMap.size(); i++) { - final String fallbackName = fallbackListMap.keyAt(i); - final List<FontFamily> familyList = fallbackListMap.valueAt(i); - final FontFamily[] families = familyList.toArray(new FontFamily[familyList.size()]); - - fallbackMap.put(fallbackName, families); + // Then, add fallback fonts to the each fallback map. + for (int i = 0; i < xmlFamilies.size(); i++) { + final FontConfig.Family xmlFamily = xmlFamilies.get(i); + // The first family (usually the sans-serif family) is always placed immediately + // after the primary family in the fallback. + if (i == 0 || xmlFamily.getFallbackName() == null) { + pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache); } - - final ArrayList<FontConfig.Alias> list = new ArrayList<>(); - list.addAll(Arrays.asList(fontConfig.getAliases())); - list.addAll(oemCustomization.mAdditionalAliases); - return list.toArray(new FontConfig.Alias[list.size()]); - } catch (IOException | XmlPullParserException e) { - Log.e(TAG, "Failed initialize system fallbacks.", e); - return ArrayUtils.emptyArray(FontConfig.Alias.class); } - } - private static FontCustomizationParser.Result readFontCustomization( - @NonNull String customizeXml, @NonNull String customFontsDir) { - try (FileInputStream f = new FileInputStream(customizeXml)) { - return FontCustomizationParser.parse(f, customFontsDir); - } catch (IOException e) { - return new FontCustomizationParser.Result(); - } catch (XmlPullParserException e) { - Log.e(TAG, "Failed to parse font customization XML", e); - return new FontCustomizationParser.Result(); + // Build the font map and fallback map. + for (int i = 0; i < fallbackListMap.size(); i++) { + final String fallbackName = fallbackListMap.keyAt(i); + final List<FontFamily> familyList = fallbackListMap.valueAt(i); + fallbackMap.put(fallbackName, familyList.toArray(new FontFamily[0])); } + + return fallbackMap; } - /** @hide */ - public static @NonNull Pair<FontConfig.Alias[], Map<String, FontFamily[]>> - initializePreinstalledFonts() { - return initializeSystemFonts(null); + /** + * Build the system Typeface mappings from FontConfig and FallbackMap. + * @hide + */ + @VisibleForTesting + public static Map<String, Typeface> buildSystemTypefaces( + FontConfig fontConfig, + Map<String, FontFamily[]> fallbackMap) { + final HashMap<String, Typeface> result = new HashMap<>(); + Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result); + return result; } - /** @hide */ - public static Pair<FontConfig.Alias[], Map<String, FontFamily[]>> - initializeSystemFonts(@Nullable Map<String, File> updatableFontMap) { - final FontCustomizationParser.Result oemCustomization = - readFontCustomization("/product/etc/fonts_customization.xml", "/product/fonts/"); - Map<String, FontFamily[]> map = new ArrayMap<>(); - FontConfig.Alias[] aliases = buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", - updatableFontMap, oemCustomization, map); + /** + * @hide + */ + public void resetFallbackMapping(Map<String, FontFamily[]> fallbackMap) { synchronized (LOCK) { - sFamilyMap = map; + sFamilyMap = fallbackMap; } - return new Pair(aliases, map); } } diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java index 633c0c417192..a0d9e8e0a6fb 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -26,7 +26,6 @@ import android.graphics.fonts.SystemFonts; import android.os.SharedMemory; import android.system.ErrnoException; import android.text.FontConfig; -import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -40,7 +39,6 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.NioUtils; import java.nio.channels.FileChannel; -import java.util.HashMap; import java.util.Map; /** A service for managing system fonts. */ @@ -69,7 +67,10 @@ public final class FontManagerService { @Override @Nullable public SharedMemory getSerializedSystemFontMap() { - return mService.getSerializedSystemFontMap(); + if (!Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) { + return null; + } + return mService.getCurrentFontSettings().getSerializedSystemFontMap(); } }); } @@ -98,48 +99,20 @@ public final class FontManagerService { private final UpdatableFontDir mUpdatableFontDir; @GuardedBy("FontManagerService.this") - @Nullable - private SharedMemory mSerializedSystemFontMap = null; + @Nullable SystemFontSettings mCurrentFontSettings = null; private FontManagerService() { mUpdatableFontDir = ENABLE_FONT_UPDATES ? new UpdatableFontDir(new File(FONT_FILES_DIR), new OtfFontFileParser()) : null; } - @Nullable - private SharedMemory getSerializedSystemFontMap() { - if (!Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) { - return null; - } + @NonNull private SystemFontSettings getCurrentFontSettings() { synchronized (FontManagerService.this) { - if (mSerializedSystemFontMap == null) { - mSerializedSystemFontMap = createSerializedSystemFontMapLocked(); - } - return mSerializedSystemFontMap; - } - } - - @Nullable - private SharedMemory createSerializedSystemFontMapLocked() { - if (mUpdatableFontDir != null) { - HashMap<String, Typeface> systemFontMap = new HashMap<>(); - Map<String, File> fontFileMap = mUpdatableFontDir.getFontFileMap(); - Pair<FontConfig.Alias[], Map<String, FontFamily[]>> pair = - SystemFonts.initializeSystemFonts(fontFileMap); - Typeface.initSystemDefaultTypefaces(systemFontMap, pair.second, pair.first); - try { - return Typeface.serializeFontMap(systemFontMap); - } catch (IOException | ErrnoException e) { - Slog.w(TAG, "Failed to serialize updatable font map. " - + "Retrying with system image fonts.", e); + if (mCurrentFontSettings == null) { + mCurrentFontSettings = SystemFontSettings.create(mUpdatableFontDir); } + return mCurrentFontSettings; } - try { - return Typeface.serializeFontMap(Typeface.getSystemFontMap()); - } catch (IOException | ErrnoException e) { - Slog.e(TAG, "Failed to serialize SystemServer system font map", e); - } - return null; } private boolean installFontFile(String name, FileDescriptor fd) { @@ -152,8 +125,74 @@ public final class FontManagerService { return false; } // Create updated font map in the next getSerializedSystemFontMap() call. - mSerializedSystemFontMap = null; + mCurrentFontSettings = null; return true; } } + + private static class SystemFontSettings { + private final @NonNull SharedMemory mSerializedSystemFontMap; + private final @NonNull FontConfig mSystemFontConfig; + private final @NonNull Map<String, FontFamily[]> mSystemFallbackMap; + private final @NonNull Map<String, Typeface> mSystemTypefaceMap; + + SystemFontSettings( + @NonNull SharedMemory serializedSystemFontMap, + @NonNull FontConfig systemFontConfig, + @NonNull Map<String, FontFamily[]> systemFallbackMap, + @NonNull Map<String, Typeface> systemTypefaceMap) { + mSerializedSystemFontMap = serializedSystemFontMap; + mSystemFontConfig = systemFontConfig; + mSystemFallbackMap = systemFallbackMap; + mSystemTypefaceMap = systemTypefaceMap; + } + + public @NonNull SharedMemory getSerializedSystemFontMap() { + return mSerializedSystemFontMap; + } + + public @NonNull FontConfig getSystemFontConfig() { + return mSystemFontConfig; + } + + public @NonNull Map<String, FontFamily[]> getSystemFallbackMap() { + return mSystemFallbackMap; + } + + public @NonNull Map<String, Typeface> getSystemTypefaceMap() { + return mSystemTypefaceMap; + } + + public static @Nullable SystemFontSettings create( + @Nullable UpdatableFontDir updatableFontDir) { + if (updatableFontDir != null) { + final FontConfig fontConfig = SystemFonts.getSystemFontConfig( + updatableFontDir.getFontFileMap()); + final Map<String, FontFamily[]> fallback = + SystemFonts.buildSystemFallback(fontConfig); + final Map<String, Typeface> typefaceMap = + SystemFonts.buildSystemTypefaces(fontConfig, fallback); + + try { + final SharedMemory shm = Typeface.serializeFontMap(typefaceMap); + return new SystemFontSettings(shm, fontConfig, fallback, typefaceMap); + } catch (IOException | ErrnoException e) { + Slog.w(TAG, "Failed to serialize updatable font map. " + + "Retrying with system image fonts.", e); + } + } + + final FontConfig fontConfig = SystemFonts.getSystemPreinstalledFontConfig(); + final Map<String, FontFamily[]> fallback = SystemFonts.buildSystemFallback(fontConfig); + final Map<String, Typeface> typefaceMap = + SystemFonts.buildSystemTypefaces(fontConfig, fallback); + try { + final SharedMemory shm = Typeface.serializeFontMap(typefaceMap); + return new SystemFontSettings(shm, fontConfig, fallback, typefaceMap); + } catch (IOException | ErrnoException e) { + Slog.e(TAG, "Failed to serialize SystemServer system font map", e); + } + return null; + } + }; } |