diff --git a/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java index 8c7695ad5a5af..fbc951e2f6e61 100644 --- a/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java +++ b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java @@ -16,6 +16,7 @@ package android.graphics.fonts; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; @@ -213,6 +214,16 @@ public final class FontFamilyUpdateRequest { public List getAxes() { return mAxes; } + + /** + * Returns the index of collection + * + * TODO(183752879): Make font index configurable and make this SystemApi. + * @hide + */ + public @IntRange(from = 0) int getIndex() { + return 0; + } } /** diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java index b79c8f62d4920..4dd5a72d446e4 100644 --- a/core/java/android/graphics/fonts/FontUpdateRequest.java +++ b/core/java/android/graphics/fonts/FontUpdateRequest.java @@ -17,19 +17,24 @@ package android.graphics.fonts; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; -import android.os.LocaleList; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.text.FontConfig; +import android.util.TypedXmlSerializer; -import java.io.File; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Represents a font update request. Currently only font install request is supported. @@ -47,6 +52,273 @@ public final class FontUpdateRequest implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface Type {} + /** + * Font object used for update. + * + * Here is an example of Family/Font XML. + * + * + * + * + * + * + * + * @see Font#readFromXml(XmlPullParser) + * @see Font#writeToXml(TypedXmlSerializer, Font) + * @see Family#readFromXml(XmlPullParser) + * @see Family#writeFamilyToXml(TypedXmlSerializer, Family) + */ + public static final class Font implements Parcelable { + private static final String ATTR_INDEX = "index"; + private static final String ATTR_WEIGHT = "weight"; + private static final String ATTR_SLANT = "slant"; + private static final String ATTR_AXIS = "axis"; + private static final String ATTR_POSTSCRIPT_NAME = "name"; + + private final @NonNull String mPostScriptName; + private final @NonNull FontStyle mFontStyle; + private final @IntRange(from = 0) int mIndex; + private final @NonNull String mFontVariationSettings; + + public Font(@NonNull String postScriptName, @NonNull FontStyle fontStyle, + @IntRange(from = 0) int index, @NonNull String fontVariationSettings) { + mPostScriptName = postScriptName; + mFontStyle = fontStyle; + mIndex = index; + mFontVariationSettings = fontVariationSettings; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString8(mPostScriptName); + dest.writeInt(mFontStyle.getWeight()); + dest.writeInt(mFontStyle.getSlant()); + dest.writeInt(mIndex); + dest.writeString8(mFontVariationSettings); + } + + public static final @NonNull Creator CREATOR = new Creator() { + @Override + public Font createFromParcel(Parcel source) { + String fontName = source.readString8(); + int weight = source.readInt(); + int slant = source.readInt(); + int index = source.readInt(); + String varSettings = source.readString8(); + return new Font(fontName, new FontStyle(weight, slant), index, varSettings); + } + + @Override + public Font[] newArray(int size) { + return new Font[size]; + } + }; + + /** + * Write {@link Font} instance to XML file. + * + * For the XML format, see {@link Font} class comment. + * + * @param out output XML serializer + * @param font a Font instance to be written. + */ + public static void writeToXml(TypedXmlSerializer out, Font font) throws IOException { + out.attribute(null, ATTR_POSTSCRIPT_NAME, font.getPostScriptName()); + out.attributeInt(null, ATTR_INDEX, font.getIndex()); + out.attributeInt(null, ATTR_WEIGHT, font.getFontStyle().getWeight()); + out.attributeInt(null, ATTR_SLANT, font.getFontStyle().getSlant()); + out.attribute(null, ATTR_AXIS, font.getFontVariationSettings()); + } + + /** + * Read {@link Font} instance from <font> element in XML + * + * For the XML format, see {@link Font} class comment. + * + * @param parser a parser that point <font> element. + * @return a font instance + * @throws IOException if font element is invalid. + */ + public static Font readFromXml(XmlPullParser parser) throws IOException { + String psName = parser.getAttributeValue(null, ATTR_POSTSCRIPT_NAME); + if (psName == null) { + throw new IOException("name attribute is missing font tag."); + } + int index = getAttributeValueInt(parser, ATTR_INDEX, 0); + int weight = getAttributeValueInt(parser, ATTR_WEIGHT, FontStyle.FONT_WEIGHT_NORMAL); + int slant = getAttributeValueInt(parser, ATTR_SLANT, FontStyle.FONT_SLANT_UPRIGHT); + String varSettings = parser.getAttributeValue(null, ATTR_AXIS); + if (varSettings == null) { + varSettings = ""; + } + return new Font(psName, new FontStyle(weight, slant), index, varSettings); + } + + public @NonNull String getPostScriptName() { + return mPostScriptName; + } + + public @NonNull FontStyle getFontStyle() { + return mFontStyle; + } + + public @IntRange(from = 0) int getIndex() { + return mIndex; + } + + public @NonNull String getFontVariationSettings() { + return mFontVariationSettings; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Font font = (Font) o; + return mIndex == font.mIndex + && mPostScriptName.equals(font.mPostScriptName) + && mFontStyle.equals(font.mFontStyle) + && mFontVariationSettings.equals(font.mFontVariationSettings); + } + + @Override + public int hashCode() { + return Objects.hash(mPostScriptName, mFontStyle, mIndex, mFontVariationSettings); + } + + @Override + public String toString() { + return "Font{" + + "mPostScriptName='" + mPostScriptName + '\'' + + ", mFontStyle=" + mFontStyle + + ", mIndex=" + mIndex + + ", mFontVariationSettings='" + mFontVariationSettings + '\'' + + '}'; + } + } + + /** + * Font Family object used for update request. + */ + public static final class Family implements Parcelable { + private static final String TAG_FAMILY = "family"; + private static final String ATTR_NAME = "name"; + private static final String TAG_FONT = "font"; + + private final @Nullable String mName; + private final @NonNull List mFonts; + + public Family(String name, List fonts) { + mName = name; + mFonts = fonts; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString8(mName); + dest.writeParcelableList(mFonts, flags); + } + + public static final @NonNull Creator CREATOR = new Creator() { + + @Override + public Family createFromParcel(Parcel source) { + String familyName = source.readString8(); + List fonts = source.readParcelableList( + new ArrayList<>(), Font.class.getClassLoader()); + return new Family(familyName, fonts); + } + + @Override + public Family[] newArray(int size) { + return new Family[size]; + } + }; + + /** + * Write {@link Family} instance to XML. + * + * For the XML format, see {@link Font} class comment. + * + * @param out an output XML serializer + * @param family a {@link Family} instance to be written + */ + public static void writeFamilyToXml(@NonNull TypedXmlSerializer out, @NonNull Family family) + throws IOException { + out.attribute(null, ATTR_NAME, family.getName()); + List fonts = family.getFonts(); + for (int i = 0; i < fonts.size(); ++i) { + Font font = fonts.get(i); + out.startTag(null, TAG_FONT); + Font.writeToXml(out, font); + out.endTag(null, TAG_FONT); + } + } + + /** + * Read a {@link Family} instance from <family> element in XML + * + * For the XML format, see {@link Font} class comment. + * + * @param parser an XML parser that points <family> element. + * @return an {@link Family} instance + */ + public static @NonNull Family readFromXml(@NonNull XmlPullParser parser) + throws XmlPullParserException, IOException { + List fonts = new ArrayList<>(); + if (parser.getEventType() != XmlPullParser.START_TAG + || !parser.getName().equals(TAG_FAMILY)) { + throw new IOException("Unexpected parser state: must be START_TAG with family"); + } + String name = parser.getAttributeValue(null, ATTR_NAME); + int type = 0; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_FONT)) { + fonts.add(Font.readFromXml(parser)); + } else if (type == XmlPullParser.END_TAG && parser.getName().equals(TAG_FAMILY)) { + break; + } + } + return new Family(name, fonts); + } + + public @NonNull String getName() { + return mName; + } + + public @NonNull List getFonts() { + return mFonts; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Family family = (Family) o; + return mName.equals(family.mName) && mFonts.equals(family.mFonts); + } + + @Override + public int hashCode() { + return Objects.hash(mName, mFonts); + } + + @Override + public String toString() { + return "Family{mName='" + mName + '\'' + ", mFonts=" + mFonts + '}'; + } + } + public static final Creator CREATOR = new Creator() { @Override public FontUpdateRequest createFromParcel(Parcel in) { @@ -68,7 +340,7 @@ public final class FontUpdateRequest implements Parcelable { private final byte[] mSignature; // NonNull if mType == TYPE_UPDATE_FONT_FAMILY. @Nullable - private final FontConfig.FontFamily mFontFamily; + private final Family mFontFamily; public FontUpdateRequest(@NonNull ParcelFileDescriptor fd, @NonNull byte[] signature) { mType = TYPE_UPDATE_FONT_FILE; @@ -77,31 +349,29 @@ public final class FontUpdateRequest implements Parcelable { mFontFamily = null; } - public FontUpdateRequest(@NonNull FontConfig.FontFamily fontFamily) { + public FontUpdateRequest(@NonNull Family fontFamily) { mType = TYPE_UPDATE_FONT_FAMILY; mFd = null; mSignature = null; mFontFamily = fontFamily; } - public FontUpdateRequest(@NonNull String postScriptName, + public FontUpdateRequest(@NonNull String familyName, @NonNull List variations) { - // TODO: Serialize the request directly instead of reusing FontConfig.FontFamily. - this(createFontFamily(postScriptName, variations)); + this(createFontFamily(familyName, variations)); } - private static FontConfig.FontFamily createFontFamily(@NonNull String postScriptName, + private static Family createFontFamily(@NonNull String familyName, @NonNull List fonts) { - List configFonts = new ArrayList<>(fonts.size()); + List updateFonts = new ArrayList<>(fonts.size()); for (FontFamilyUpdateRequest.Font font : fonts) { - // TODO: Support .otf. - configFonts.add(new FontConfig.Font(new File(font.getPostScriptName() + ".ttf"), null, - font.getStyle(), 0 /* index */, - FontVariationAxis.toFontVariationSettings(font.getAxes()), - null /* fontFamilyName */)); + updateFonts.add(new Font( + font.getPostScriptName(), + font.getStyle(), + font.getIndex(), + FontVariationAxis.toFontVariationSettings(font.getAxes()))); } - return new FontConfig.FontFamily(configFonts, postScriptName, - LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT); + return new Family(familyName, updateFonts); } protected FontUpdateRequest(Parcel in) { @@ -126,7 +396,7 @@ public final class FontUpdateRequest implements Parcelable { } @Nullable - public FontConfig.FontFamily getFontFamily() { + public Family getFontFamily() { return mFontFamily; } @@ -142,4 +412,17 @@ public final class FontUpdateRequest implements Parcelable { dest.writeBlob(mSignature); dest.writeParcelable(mFontFamily, flags); } + + // Utility functions + private static int getAttributeValueInt(XmlPullParser parser, String name, int defaultValue) { + try { + String value = parser.getAttributeValue(null, name); + if (value == null) { + return defaultValue; + } + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } } diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java index 7bc81cd2f9280..bef1a4e7010fa 100644 --- a/core/tests/coretests/src/android/graphics/FontListParserTest.java +++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java @@ -24,14 +24,12 @@ import static android.text.FontConfig.FontFamily.VARIANT_DEFAULT; import static android.text.FontConfig.FontFamily.VARIANT_ELEGANT; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; import static junit.framework.Assert.fail; import android.graphics.fonts.FontStyle; import android.os.LocaleList; import android.text.FontConfig; -import android.util.TypedXmlSerializer; import android.util.Xml; import androidx.test.filters.SmallTest; @@ -43,7 +41,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -69,10 +66,6 @@ public final class FontListParserTest { FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); - - String serialized = writeFamily(family); - assertWithMessage("serialized = " + serialized) - .that(readFamily(serialized)).isEqualTo(expected); } @Test @@ -94,10 +87,6 @@ public final class FontListParserTest { FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); - - String serialized = writeFamily(family); - assertWithMessage("serialized = " + serialized) - .that(readFamily(serialized)).isEqualTo(expected); } @Test @@ -115,10 +104,6 @@ public final class FontListParserTest { FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); - - String serialized = writeFamily(family); - assertWithMessage("serialized = " + serialized) - .that(readFamily(serialized)).isEqualTo(expected); } @Test @@ -136,10 +121,6 @@ public final class FontListParserTest { FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); - - String serialized = writeFamily(family); - assertWithMessage("serialized = " + serialized) - .that(readFamily(serialized)).isEqualTo(expected); } @Test @@ -164,10 +145,6 @@ public final class FontListParserTest { "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT); FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); - - String serialized = writeFamily(family); - assertWithMessage("serialized = " + serialized) - .that(readFamily(serialized)).isEqualTo(expected); } @Test @@ -194,10 +171,6 @@ public final class FontListParserTest { "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT); FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); - - String serialized = writeFamily(family); - assertWithMessage("serialized = " + serialized) - .that(readFamily(serialized)).isEqualTo(expected); } @Test @@ -218,10 +191,6 @@ public final class FontListParserTest { "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT); FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); - - String serialized = writeFamily(family); - assertWithMessage("serialized = " + serialized) - .that(readFamily(serialized)).isEqualTo(expected); } @Test @@ -338,15 +307,4 @@ public final class FontListParserTest { parser.nextTag(); return FontListParser.readFamily(parser, "", null); } - - private String writeFamily(FontConfig.FontFamily family) throws IOException { - TypedXmlSerializer out = Xml.newFastSerializer(); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - out.setOutput(buffer, "UTF-8"); - out.startTag(null, "family"); - FontListParser.writeFamily(out, family); - out.endTag(null, "family"); - out.endDocument(); - return buffer.toString("UTF-8"); - } } diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 95c7715a16884..6fdf5526edf57 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -25,8 +25,6 @@ import android.graphics.fonts.FontVariationAxis; import android.os.Build; import android.os.LocaleList; import android.text.FontConfig; -import android.text.TextUtils; -import android.util.TypedXmlSerializer; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; @@ -194,32 +192,6 @@ public class FontListParser { return new FontConfig.FontFamily(fonts, name, LocaleList.forLanguageTags(lang), intVariant); } - /** - * Write a family tag representing {@code fontFamily}. The tag should be started by the caller. - */ - public static void writeFamily(TypedXmlSerializer out, FontConfig.FontFamily fontFamily) - throws IOException { - if (!TextUtils.isEmpty(fontFamily.getName())) { - out.attribute(null, ATTR_NAME, fontFamily.getName()); - } - if (!fontFamily.getLocaleList().isEmpty()) { - out.attribute(null, ATTR_LANG, fontFamily.getLocaleList().toLanguageTags()); - } - switch (fontFamily.getVariant()) { - case FontConfig.FontFamily.VARIANT_COMPACT: - out.attribute(null, ATTR_VARIANT, VARIANT_COMPACT); - break; - case FontConfig.FontFamily.VARIANT_ELEGANT: - out.attribute(null, ATTR_VARIANT, VARIANT_ELEGANT); - break; - } - for (FontConfig.Font font : fontFamily.getFontList()) { - out.startTag(null, TAG_FONT); - writeFont(out, font); - out.endTag(null, TAG_FONT); - } - } - /** Matches leading and trailing XML whitespace. */ private static final Pattern FILENAME_WHITESPACE_PATTERN = Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$"); @@ -292,34 +264,6 @@ public class FontListParser { return null; } - private static void writeFont(TypedXmlSerializer out, FontConfig.Font font) - throws IOException { - if (font.getTtcIndex() != 0) { - out.attributeInt(null, ATTR_INDEX, font.getTtcIndex()); - } - if (font.getStyle().getWeight() != FontStyle.FONT_WEIGHT_NORMAL) { - out.attributeInt(null, ATTR_WEIGHT, font.getStyle().getWeight()); - } - if (font.getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC) { - out.attribute(null, ATTR_STYLE, STYLE_ITALIC); - } else { - out.attribute(null, ATTR_STYLE, STYLE_NORMAL); - } - if (!TextUtils.isEmpty(font.getFontFamilyName())) { - out.attribute(null, ATTR_FALLBACK_FOR, font.getFontFamilyName()); - } - out.text(font.getFile().getName()); - FontVariationAxis[] axes = - FontVariationAxis.fromFontVariationSettings(font.getFontVariationSettings()); - if (axes != null) { - for (FontVariationAxis axis : axes) { - out.startTag(null, TAG_AXIS); - writeAxis(out, axis); - out.endTag(null, TAG_AXIS); - } - } - } - private static FontVariationAxis readAxis(XmlPullParser parser) throws XmlPullParserException, IOException { String tagStr = parser.getAttributeValue(null, ATTR_TAG); @@ -328,12 +272,6 @@ public class FontListParser { return new FontVariationAxis(tagStr, Float.parseFloat(styleValueStr)); } - private static void writeAxis(TypedXmlSerializer out, FontVariationAxis axis) - throws IOException { - out.attribute(null, ATTR_TAG, axis.getTag()); - out.attributeFloat(null, ATTR_STYLEVALUE, axis.getStyleValue()); - } - /** * Reads alias elements */ diff --git a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java index 62337c7df03b7..15abbd587bbdd 100644 --- a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java +++ b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java @@ -17,8 +17,7 @@ package com.android.server.graphics.fonts; import android.annotation.NonNull; -import android.graphics.FontListParser; -import android.text.FontConfig; +import android.graphics.fonts.FontUpdateRequest; import android.text.TextUtils; import android.util.ArraySet; import android.util.Slog; @@ -48,7 +47,7 @@ import java.util.Set; /* package */ static class Config { public long lastModifiedMillis; public final Set updatedFontDirs = new ArraySet<>(); - public final List fontFamilies = new ArrayList<>(); + public final List fontFamilies = new ArrayList<>(); } /** @@ -81,8 +80,8 @@ import java.util.Set; case TAG_FAMILY: // updatableFontMap is not ready here. We get the base file names by passing // empty fontDir, and resolve font paths later. - out.fontFamilies.add(FontListParser.readFamily( - parser, "" /* fontDir */, null /* updatableFontMap */)); + out.fontFamilies.add(FontUpdateRequest.Family.readFromXml(parser)); + break; default: Slog.w(TAG, "Skipping unknown tag: " + tag); } @@ -108,11 +107,11 @@ import java.util.Set; out.attribute(null, ATTR_VALUE, dir); out.endTag(null, TAG_UPDATED_FONT_DIR); } - List fontFamilies = config.fontFamilies; + List fontFamilies = config.fontFamilies; for (int i = 0; i < fontFamilies.size(); i++) { - FontConfig.FontFamily fontFamily = fontFamilies.get(i); + FontUpdateRequest.Family fontFamily = fontFamilies.get(i); out.startTag(null, TAG_FAMILY); - FontListParser.writeFamily(out, fontFamily); + FontUpdateRequest.Family.writeFamilyToXml(out, fontFamily); out.endTag(null, TAG_FAMILY); } out.endTag(null, TAG_ROOT); diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java index 75181307dc42a..3e6ddc39909ec 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -31,8 +31,6 @@ import android.util.ArrayMap; import android.util.Base64; import android.util.Slog; -import com.android.internal.annotations.VisibleForTesting; - import org.xmlpull.v1.XmlPullParserException; import java.io.File; @@ -42,9 +40,9 @@ import java.io.FileOutputStream; import java.io.IOException; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.function.Supplier; /** @@ -97,6 +95,11 @@ final class UpdatableFontDir { public long getRevision() { return mRevision; } + + @Override + public String toString() { + return "FontFileInfo{mFile=" + mFile + ", mRevision=" + mRevision + '}'; + } } /** @@ -121,12 +124,6 @@ final class UpdatableFontDir { */ private final ArrayMap mFontFileInfoMap = new ArrayMap<>(); - /** - * A mutable map containing mapping from font family name to {@link FontConfig.FontFamily}. - * The FontFamily entries only reference font files in {@link #mFontFileInfoMap}. - */ - private final ArrayMap mFontFamilyMap = new ArrayMap<>(); - UpdatableFontDir(File filesDir, List preinstalledFontDirs, FontFileParser parser, FsverityUtil fsverityUtil) { this(filesDir, preinstalledFontDirs, parser, fsverityUtil, new File(CONFIG_XML_FILE), @@ -147,7 +144,6 @@ final class UpdatableFontDir { /* package */ void loadFontFileMap() { mFontFileInfoMap.clear(); - mFontFamilyMap.clear(); mLastModifiedMillis = 0; boolean success = false; try { @@ -155,7 +151,7 @@ final class UpdatableFontDir { try (FileInputStream fis = new FileInputStream(mConfigFile)) { PersistentSystemFontConfig.loadFromXml(fis, config); } catch (IOException | XmlPullParserException e) { - Slog.e(TAG, "Failed to load config xml file", e); + // The font config file is missing on the first boot. Just do nothing. return; } mLastModifiedMillis = config.lastModifiedMillis; @@ -180,17 +176,6 @@ final class UpdatableFontDir { FontFileInfo fontFileInfo = validateFontFile(files[0]); addFileToMapIfNewer(fontFileInfo, true /* deleteOldFile */); } - // Resolve font file paths. - List fontFamilies = config.fontFamilies; - for (int i = 0; i < fontFamilies.size(); i++) { - FontConfig.FontFamily fontFamily = fontFamilies.get(i); - try { - addFontFamily(fontFamily); - } catch (SystemFontException e) { - // Ignore failures as updated fonts may be obsoleted by system OTA update. - Slog.i(TAG, "Obsolete font family: " + fontFamily.getName()); - } - } success = true; } catch (Throwable t) { // If something happened during loading system fonts, clear all contents in finally @@ -200,7 +185,6 @@ final class UpdatableFontDir { // Delete all files just in case if we find a problematic file. if (!success) { mFontFileInfoMap.clear(); - mFontFamilyMap.clear(); mLastModifiedMillis = 0; FileUtils.deleteContents(mFilesDir); } @@ -210,11 +194,12 @@ final class UpdatableFontDir { /* package */ void clearUpdates() throws SystemFontException { mFontFileInfoMap.clear(); FileUtils.deleteContents(mFilesDir); - mFontFamilyMap.clear(); mLastModifiedMillis = mCurrentTimeSupplier.get(); try (FileOutputStream fos = new FileOutputStream(mConfigFile)) { - PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig()); + PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); + config.lastModifiedMillis = mLastModifiedMillis; + PersistentSystemFontConfig.writeToXml(fos, config); } catch (Exception e) { throw new SystemFontException( FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG, @@ -231,7 +216,13 @@ final class UpdatableFontDir { public void update(List requests) throws SystemFontException { // Backup the mapping for rollback. ArrayMap backupMap = new ArrayMap<>(mFontFileInfoMap); - ArrayMap backupFamilies = new ArrayMap<>(mFontFamilyMap); + PersistentSystemFontConfig.Config curConfig = readPersistentConfig(); + Map familyMap = new HashMap<>(); + for (int i = 0; i < curConfig.fontFamilies.size(); ++i) { + FontUpdateRequest.Family family = curConfig.fontFamilies.get(i); + familyMap.put(family.getName(), family); + } + long backupLastModifiedDate = mLastModifiedMillis; boolean success = false; try { @@ -242,15 +233,33 @@ final class UpdatableFontDir { request.getFd().getFileDescriptor(), request.getSignature()); break; case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY: - addFontFamily(request.getFontFamily()); + FontUpdateRequest.Family family = request.getFontFamily(); + familyMap.put(family.getName(), family); break; } } + // Before processing font family update, check all family points the available fonts. + for (FontUpdateRequest.Family family : familyMap.values()) { + if (resolveFontFiles(family) == null) { + throw new SystemFontException( + FontManager.RESULT_ERROR_FONT_NOT_FOUND, + "Required fonts are not available"); + } + } + // Write config file. mLastModifiedMillis = mCurrentTimeSupplier.get(); + + curConfig.lastModifiedMillis = mLastModifiedMillis; + for (FontFileInfo info : mFontFileInfoMap.values()) { + curConfig.updatedFontDirs.add(info.getRandomizedFontDir().getName()); + } + curConfig.fontFamilies.clear(); + curConfig.fontFamilies.addAll(familyMap.values()); + try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) { - PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig()); + PersistentSystemFontConfig.writeToXml(fos, curConfig); } catch (Exception e) { throw new SystemFontException( FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG, @@ -268,8 +277,6 @@ final class UpdatableFontDir { if (!success) { mFontFileInfoMap.clear(); mFontFileInfoMap.putAll(backupMap); - mFontFamilyMap.clear(); - mFontFamilyMap.putAll(backupFamilies); mLastModifiedMillis = backupLastModifiedDate; } } @@ -511,50 +518,22 @@ final class UpdatableFontDir { } } - /** - * Adds a font family to {@link #mFontFamilyMap} and returns true on success. - * - *

This method only accepts adding or updating a font family with a name. - * This is to prevent bad font family update from removing glyphs from font fallback chains. - * Unnamed font families are used as other named font family's fallback fonts to guarantee a - * complete glyph coverage. - */ - private void addFontFamily(FontConfig.FontFamily fontFamily) throws SystemFontException { - Objects.requireNonNull(fontFamily.getName()); - FontConfig.FontFamily resolvedFontFamily = resolveFontFiles(fontFamily); - if (resolvedFontFamily == null) { - throw new SystemFontException( - FontManager.RESULT_ERROR_FONT_NOT_FOUND, - "Required fonts are not available"); - } - mFontFamilyMap.put(resolvedFontFamily.getName(), resolvedFontFamily); - } - @Nullable - private FontConfig.FontFamily resolveFontFiles(FontConfig.FontFamily fontFamily) { - List resolvedFonts = new ArrayList<>(fontFamily.getFontList().size()); - List fontList = fontFamily.getFontList(); + private FontConfig.FontFamily resolveFontFiles(FontUpdateRequest.Family fontFamily) { + List fontList = fontFamily.getFonts(); + List resolvedFonts = new ArrayList<>(fontList.size()); for (int i = 0; i < fontList.size(); i++) { - FontConfig.Font font = fontList.get(i); - FontFileInfo info = lookupFontFileInfo(font.getFile()); + FontUpdateRequest.Font font = fontList.get(i); + FontFileInfo info = mFontFileInfoMap.get(font.getPostScriptName()); if (info == null) { + Slog.e(TAG, "Failed to lookup font file that has " + font.getPostScriptName()); return null; } - resolvedFonts.add(new FontConfig.Font(info.mFile, null, font.getStyle(), - font.getTtcIndex(), font.getFontVariationSettings(), font.getFontFamilyName())); + resolvedFonts.add(new FontConfig.Font(info.mFile, null, font.getFontStyle(), + font.getIndex(), font.getFontVariationSettings(), null)); } return new FontConfig.FontFamily(resolvedFonts, fontFamily.getName(), - fontFamily.getLocaleList(), fontFamily.getVariant()); - } - - private PersistentSystemFontConfig.Config createPersistentConfig() { - PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); - config.lastModifiedMillis = mLastModifiedMillis; - for (FontFileInfo info : mFontFileInfoMap.values()) { - config.updatedFontDirs.add(info.getRandomizedFontDir().getName()); - } - config.fontFamilies.addAll(mFontFamilyMap.values()); - return config; + null, FontConfig.FontFamily.VARIANT_DEFAULT); } Map getFontFileMap() { @@ -566,27 +545,54 @@ final class UpdatableFontDir { return map; } - @VisibleForTesting - Map getFontFamilyMap() { - return mFontFamilyMap; - } - /* package */ FontConfig getSystemFontConfig() { FontConfig config = SystemFonts.getSystemFontConfig(getFontFileMap(), 0, 0); + PersistentSystemFontConfig.Config persistentConfig = readPersistentConfig(); + List families = persistentConfig.fontFamilies; + List mergedFamilies = - new ArrayList<>(config.getFontFamilies().size() + mFontFamilyMap.size()); + new ArrayList<>(config.getFontFamilies().size() + families.size()); // We should keep the first font family (config.getFontFamilies().get(0)) because it's used // as a fallback font. See SystemFonts.java. mergedFamilies.addAll(config.getFontFamilies()); // When building Typeface, a latter font family definition will override the previous font // family definition with the same name. An exception is config.getFontFamilies.get(0), // which will be used as a fallback font without being overridden. - mergedFamilies.addAll(mFontFamilyMap.values()); + for (int i = 0; i < families.size(); ++i) { + FontConfig.FontFamily family = resolveFontFiles(families.get(i)); + if (family != null) { + mergedFamilies.add(family); + } + } + return new FontConfig( mergedFamilies, config.getAliases(), mLastModifiedMillis, mConfigVersion); } + private PersistentSystemFontConfig.Config readPersistentConfig() { + PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); + try (FileInputStream fis = new FileInputStream(mConfigFile)) { + PersistentSystemFontConfig.loadFromXml(fis, config); + } catch (IOException | XmlPullParserException e) { + // The font config file is missing on the first boot. Just do nothing. + } + return config; + } + /* package */ int getConfigVersion() { return mConfigVersion; } + + public Map getFontFamilyMap() { + PersistentSystemFontConfig.Config curConfig = readPersistentConfig(); + Map familyMap = new HashMap<>(); + for (int i = 0; i < curConfig.fontFamilies.size(); ++i) { + FontUpdateRequest.Family family = curConfig.fontFamilies.get(i); + FontConfig.FontFamily resolvedFamily = resolveFontFiles(family); + if (resolvedFamily != null) { + familyMap.put(family.getName(), resolvedFamily); + } + } + return familyMap; + } } diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java index 912da94141659..63a53e79e133b 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java @@ -18,9 +18,8 @@ package com.android.server.graphics.fonts; import static com.google.common.truth.Truth.assertThat; -import android.graphics.FontListParser; +import android.graphics.fonts.FontUpdateRequest; import android.platform.test.annotations.Presubmit; -import android.text.FontConfig; import android.util.Xml; import androidx.test.filters.SmallTest; @@ -49,9 +48,9 @@ public final class PersistentSystemFontConfigTest { config.updatedFontDirs.add("~~abc"); config.updatedFontDirs.add("~~def"); - FontConfig.FontFamily fontFamily = parseFontFamily( + FontUpdateRequest.Family fontFamily = parseFontFamily( "" - + " test.ttf" + + " " + ""); config.fontFamilies.add(fontFamily); @@ -86,11 +85,11 @@ public final class PersistentSystemFontConfigTest { } } - private static FontConfig.FontFamily parseFontFamily(String xml) throws Exception { + private static FontUpdateRequest.Family parseFontFamily(String xml) throws Exception { XmlPullParser parser = Xml.newPullParser(); ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); parser.setInput(is, "UTF-8"); parser.nextTag(); - return FontListParser.readFamily(parser, "", null); + return FontUpdateRequest.Family.readFromXml(parser); } } diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java index e7ffea0a650dc..b941b23e11ad4 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java @@ -699,7 +699,7 @@ public final class UpdatableFontDirTest { + " test.ttf" + ""))); fail("Expect NullPointerException"); - } catch (NullPointerException e) { + } catch (FontManagerService.SystemFontException e) { // Expect } } @@ -797,8 +797,18 @@ public final class UpdatableFontDirTest { ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); parser.setInput(is, "UTF-8"); parser.nextTag(); + FontConfig.FontFamily fontFamily = FontListParser.readFamily(parser, "", null); - return new FontUpdateRequest(fontFamily); + List fonts = new ArrayList<>(); + for (FontConfig.Font font : fontFamily.getFontList()) { + String name = font.getFile().getName(); + String psName = name.substring(0, name.length() - 4); // drop suffix + FontUpdateRequest.Font updateFont = new FontUpdateRequest.Font( + psName, font.getStyle(), font.getTtcIndex(), font.getFontVariationSettings()); + fonts.add(updateFont); + } + FontUpdateRequest.Family family = new FontUpdateRequest.Family(fontFamily.getName(), fonts); + return new FontUpdateRequest(family); } private void writeConfig(PersistentSystemFontConfig.Config config,