From 87036d61ecbaba9131a666f5104ffff758d0a526 Mon Sep 17 00:00:00 2001 From: Kohsuke Yatoh Date: Thu, 7 Jan 2021 19:39:10 -0800 Subject: [PATCH] Check updatable font dir in FontListParser. If updatable font dir is provided by caller and fonts exist in the dir, FontListParser will use the updated fonts. Currently, updatable font dir is used only in test. Bug: 173619554 Test: atest TypefaceSystemFallbackTest Change-Id: I631764fe235d258b6b5cb6d6906c17d48c7e4608 --- .../graphics/TypefaceSystemFallbackTest.java | 65 +++++++++++++++---- .../java/android/graphics/FontListParser.java | 40 ++++++++---- .../fonts/FontCustomizationParser.java | 2 +- .../android/graphics/fonts/SystemFonts.java | 44 ++++++++++--- 4 files changed, 113 insertions(+), 38 deletions(-) diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java index 82d066f6ab168..05ff21853131f 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java @@ -67,6 +67,7 @@ public class TypefaceSystemFallbackTest { private static final String TEST_FONT_DIR; private static final String TEST_OEM_XML; private static final String TEST_OEM_DIR; + private static final String TEST_UPDATABLE_FONT_DIR; private static final float GLYPH_1EM_WIDTH; private static final float GLYPH_2EM_WIDTH; @@ -82,9 +83,11 @@ public class TypefaceSystemFallbackTest { TEST_FONTS_XML = new File(cacheDir, "fonts.xml").getAbsolutePath(); TEST_OEM_DIR = cacheDir.getAbsolutePath() + "/oem_fonts/"; TEST_OEM_XML = new File(cacheDir, "fonts_customization.xml").getAbsolutePath(); + TEST_UPDATABLE_FONT_DIR = cacheDir.getAbsolutePath() + "/updatable_fonts/"; new File(TEST_FONT_DIR).mkdirs(); new File(TEST_OEM_DIR).mkdirs(); + new File(TEST_UPDATABLE_FONT_DIR).mkdirs(); final AssetManager am = InstrumentationRegistry.getInstrumentation().getContext().getAssets(); @@ -103,18 +106,11 @@ public class TypefaceSystemFallbackTest { InstrumentationRegistry.getInstrumentation().getContext().getAssets(); for (final String fontFile : TEST_FONT_FILES) { final String sourceInAsset = "fonts/" + fontFile; - final File outInCache = new File(TEST_FONT_DIR, fontFile); - try (InputStream is = am.open(sourceInAsset)) { - Files.copy(is, outInCache.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - throw new RuntimeException(e); - } - final File outOemInCache = new File(TEST_OEM_DIR, fontFile); - try (InputStream is = am.open(sourceInAsset)) { - Files.copy(is, outOemInCache.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - throw new RuntimeException(e); - } + copyAssetToFile(sourceInAsset, new File(TEST_FONT_DIR, fontFile)); + copyAssetToFile(sourceInAsset, new File(TEST_OEM_DIR, fontFile)); + } + for (final File fontFile : new File(TEST_UPDATABLE_FONT_DIR).listFiles()) { + fontFile.delete(); } } @@ -124,7 +120,20 @@ public class TypefaceSystemFallbackTest { final File outInCache = new File(TEST_FONT_DIR, fontFile); outInCache.delete(); final File outOemInCache = new File(TEST_OEM_DIR, fontFile); - outInCache.delete(); + outOemInCache.delete(); + } + for (final File fontFile : new File(TEST_UPDATABLE_FONT_DIR).listFiles()) { + fontFile.delete(); + } + } + + private static void copyAssetToFile(String sourceInAsset, File out) { + final AssetManager am = + InstrumentationRegistry.getInstrumentation().getContext().getAssets(); + try (InputStream is = am.open(sourceInAsset)) { + Files.copy(is, out.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new RuntimeException(e); } } @@ -138,7 +147,7 @@ public class TypefaceSystemFallbackTest { } final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(TEST_FONTS_XML, - TEST_FONT_DIR, oemCustomization, fallbackMap); + TEST_FONT_DIR, TEST_UPDATABLE_FONT_DIR, oemCustomization, fallbackMap); Typeface.initSystemDefaultTypefaces(fontMap, fallbackMap, aliases); } @@ -835,4 +844,32 @@ public class TypefaceSystemFallbackTest { + ""; readFontCustomization(oemXml); } + + + @Test + public void testBuildSystemFallback_UpdatableFont() { + final String xml = "" + + "" + + " " + + " a3em.ttf" + + " " + + ""; + final ArrayMap fontMap = new ArrayMap<>(); + final ArrayMap 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); + + final Paint paint = new Paint(); + + final Typeface sansSerifTypeface = fontMap.get("test"); + assertNotNull(sansSerifTypeface); + paint.setTypeface(sansSerifTypeface); + assertEquals(GLYPH_2EM_WIDTH, paint.measureText("a"), 0.0f); + assertEquals(GLYPH_2EM_WIDTH, paint.measureText("b"), 0.0f); + assertEquals(GLYPH_2EM_WIDTH, paint.measureText("c"), 0.0f); + } } diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 0782f8dfd9d34..73fff7207c453 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -16,6 +16,7 @@ package android.graphics; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.fonts.FontVariationAxis; import android.os.Build; @@ -25,6 +26,7 @@ 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; @@ -41,26 +43,26 @@ 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"); + return parse(in, "/system/fonts", null); } /** * Parse the fonts.xml */ - public static FontConfig parse(InputStream in, String fontDir) - throws XmlPullParserException, IOException { + public static FontConfig parse(InputStream in, String fontDir, + @Nullable String updatableFontDir) throws XmlPullParserException, IOException { try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, null); parser.nextTag(); - return readFamilies(parser, fontDir); + return readFamilies(parser, fontDir, updatableFontDir); } finally { in.close(); } } - private static FontConfig readFamilies(XmlPullParser parser, String fontDir) - throws XmlPullParserException, IOException { + private static FontConfig readFamilies(XmlPullParser parser, String fontDir, + @Nullable String updatableFontDir) throws XmlPullParserException, IOException { List families = new ArrayList<>(); List aliases = new ArrayList<>(); @@ -69,7 +71,7 @@ public class FontListParser { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("family")) { - families.add(readFamily(parser, fontDir)); + families.add(readFamily(parser, fontDir, updatableFontDir)); } else if (tag.equals("alias")) { aliases.add(readAlias(parser)); } else { @@ -83,8 +85,8 @@ public class FontListParser { /** * Reads a family element */ - public static FontConfig.Family readFamily(XmlPullParser parser, String fontDir) - throws XmlPullParserException, IOException { + public static FontConfig.Family readFamily(XmlPullParser parser, String fontDir, + @Nullable String updatableFontDir) throws XmlPullParserException, IOException { final String name = parser.getAttributeValue(null, "name"); final String lang = parser.getAttributeValue("", "lang"); final String variant = parser.getAttributeValue(null, "variant"); @@ -93,7 +95,7 @@ public class FontListParser { if (parser.getEventType() != XmlPullParser.START_TAG) continue; final String tag = parser.getName(); if (tag.equals("font")) { - fonts.add(readFont(parser, fontDir)); + fonts.add(readFont(parser, fontDir, updatableFontDir)); } else { skip(parser); } @@ -114,8 +116,8 @@ public class FontListParser { private static final Pattern FILENAME_WHITESPACE_PATTERN = Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$"); - private static FontConfig.Font readFont(XmlPullParser parser, String fontDir) - throws XmlPullParserException, IOException { + private static FontConfig.Font readFont(XmlPullParser parser, String fontDir, + @Nullable String updatableFontDir) throws XmlPullParserException, IOException { String indexStr = parser.getAttributeValue(null, "index"); int index = indexStr == null ? 0 : Integer.parseInt(indexStr); List axes = new ArrayList(); @@ -137,10 +139,22 @@ public class FontListParser { } } String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll(""); - return new FontConfig.Font(fontDir + sanitizedName, index, axes.toArray( + String fontName = findFontFile(sanitizedName, fontDir, updatableFontDir); + return new FontConfig.Font(fontName, index, axes.toArray( new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor); } + private static String findFontFile(String fileName, String fontDir, + @Nullable String updatableFontDir) { + if (updatableFontDir != null) { + String updatableFontName = updatableFontDir + fileName; + if (new File(updatableFontName).exists()) { + return updatableFontName; + } + } + return fontDir + fileName; + } + private static FontVariationAxis readAxis(XmlPullParser parser) throws XmlPullParserException, IOException { String tagStr = parser.getAttributeValue(null, "tag"); diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java index 0291d7484dc5f..f95da82ee07c9 100644 --- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java +++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java @@ -97,7 +97,7 @@ public class FontCustomizationParser { throw new IllegalArgumentException("customizationType must be specified"); } if (customizationType.equals("new-named-family")) { - out.mAdditionalNamedFamilies.add(FontListParser.readFamily(parser, fontDir)); + out.mAdditionalNamedFamilies.add(FontListParser.readFamily(parser, fontDir, null)); } else { throw new IllegalArgumentException("Unknown customizationType=" + customizationType); } diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index fb6ea99be7ab6..16a53c25db08e 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -91,7 +91,9 @@ public final class SystemFonts { final FontCustomizationParser.Result oemCustomization = readFontCustomization("/product/etc/fonts_customization.xml", "/product/fonts/"); Map map = new ArrayMap<>(); - buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", oemCustomization, map); + // TODO: use updated fonts + buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", null /* updatableFontDir */, + oemCustomization, map); Set res = new HashSet<>(); for (FontFamily[] families : map.values()) { for (FontFamily family : families) { @@ -226,13 +228,7 @@ public final class SystemFonts { } /** - * 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 fallbackMap An output system fallback map. Caller must pass empty map. - * @return a list of aliases + * @see #buildSystemFallback(String, String, String, FontCustomizationParser.Result, Map) * @hide */ @VisibleForTesting @@ -240,9 +236,31 @@ public final class SystemFonts { @NonNull String fontDir, @NonNull FontCustomizationParser.Result oemCustomization, @NonNull Map fallbackMap) { + return buildSystemFallback(xmlPath, fontDir, null /* updatableFontDir */, + oemCustomization, fallbackMap); + } + + /** + * 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 updatableFontDir A full path string to the updatable system font directory. This + * must end with slash('/'). + * @param fallbackMap An output system fallback map. Caller must pass empty map. + * @return a list of aliases + * @hide + */ + @VisibleForTesting + public static FontConfig.Alias[] buildSystemFallback(@NonNull String xmlPath, + @NonNull String fontDir, + @Nullable String updatableFontDir, + @NonNull FontCustomizationParser.Result oemCustomization, + @NonNull Map fallbackMap) { try { final FileInputStream fontsIn = new FileInputStream(xmlPath); - final FontConfig fontConfig = FontListParser.parse(fontsIn, fontDir); + final FontConfig fontConfig = FontListParser.parse(fontsIn, fontDir, updatableFontDir); final HashMap bufferCache = new HashMap(); final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies(); @@ -306,11 +324,17 @@ public final class SystemFonts { /** @hide */ public static @NonNull Pair> initializePreinstalledFonts() { + return initializeSystemFonts(null); + } + + /** @hide */ + public static Pair> + initializeSystemFonts(@Nullable String updatableFontDir) { final FontCustomizationParser.Result oemCustomization = readFontCustomization("/product/etc/fonts_customization.xml", "/product/fonts/"); Map map = new ArrayMap<>(); FontConfig.Alias[] aliases = buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", - oemCustomization, map); + updatableFontDir, oemCustomization, map); synchronized (LOCK) { sFamilyMap = map; }