Use separate XML for update config in system

This CL is a refactoring to address following things:
- Separate XML definition of fonts.xml and update.
  Since there are lots of concept mismatches between system font config
  and update config, e.g. file paths, family variants, etc.
  It is good to manage separtely.

- Stop keeping udpated config in memory in FontManagerService.
  As a side effect of above change, FontManagerService no longer keeps
  FontConfig.FontFamily mappings. This will be created from XML file
  when necessary. This is better in FontManagerService since this map
  is only necessary the boot time. After the system is booted, this
  map is only necessary when the update request is called from client
  which happens quite rare.

Bug: 183752879
Test: atest UpdatableFontDirTest
Test: atest FontManagerTest
Test: atest UpdatableSystemFontTest
Test: atest FontFamilyUpdateRequestTest
Test: atest PersistentSystemFontConfigTest

Change-Id: If2c12a8029f859efc52721b07cfeab04be2deb78
This commit is contained in:
Seigo Nonaka
2021-03-25 15:15:58 -07:00
parent d99fcedeb3
commit 38fbc56351
8 changed files with 415 additions and 211 deletions

View File

@@ -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<FontVariationAxis> 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;
}
}
/**

View File

@@ -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.
* <family name="my-sans">
* <font name="MySans" weight="400" slant="0" axis="'wght' 400 'ital' 0" index="0" />
* <font name="MySans" weight="400" slant="0" axis="'wght' 400 'ital' 1" index="0" />
* <font name="MySans" weight="400" slant="0" axis="'wght' 700 'ital' 0" index="0" />
* <font name="MySans" weight="400" slant="0" axis="'wght' 700 'ital' 1" index="0" />
* </family>
*
* @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<Font> CREATOR = new Creator<Font>() {
@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 &lt;font&gt; element in XML
*
* For the XML format, see {@link Font} class comment.
*
* @param parser a parser that point &lt;font&gt; 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<Font> mFonts;
public Family(String name, List<Font> 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<Family> CREATOR = new Creator<Family>() {
@Override
public Family createFromParcel(Parcel source) {
String familyName = source.readString8();
List<Font> 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<Font> 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 &lt;family&gt; element in XML
*
* For the XML format, see {@link Font} class comment.
*
* @param parser an XML parser that points &lt;family&gt; element.
* @return an {@link Family} instance
*/
public static @NonNull Family readFromXml(@NonNull XmlPullParser parser)
throws XmlPullParserException, IOException {
List<Font> 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<Font> 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<FontUpdateRequest> CREATOR = new Creator<FontUpdateRequest>() {
@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<FontFamilyUpdateRequest.Font> 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<FontFamilyUpdateRequest.Font> fonts) {
List<FontConfig.Font> configFonts = new ArrayList<>(fonts.size());
List<Font> 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;
}
}
}

View File

@@ -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");
}
}

View File

@@ -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
*/

View File

@@ -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<String> updatedFontDirs = new ArraySet<>();
public final List<FontConfig.FontFamily> fontFamilies = new ArrayList<>();
public final List<FontUpdateRequest.Family> 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<FontConfig.FontFamily> fontFamilies = config.fontFamilies;
List<FontUpdateRequest.Family> 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);

View File

@@ -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<String, FontFileInfo> 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<String, FontConfig.FontFamily> mFontFamilyMap = new ArrayMap<>();
UpdatableFontDir(File filesDir, List<File> 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<FontConfig.FontFamily> 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<FontUpdateRequest> requests) throws SystemFontException {
// Backup the mapping for rollback.
ArrayMap<String, FontFileInfo> backupMap = new ArrayMap<>(mFontFileInfoMap);
ArrayMap<String, FontConfig.FontFamily> backupFamilies = new ArrayMap<>(mFontFamilyMap);
PersistentSystemFontConfig.Config curConfig = readPersistentConfig();
Map<String, FontUpdateRequest.Family> 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.
*
* <p>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<FontConfig.Font> resolvedFonts = new ArrayList<>(fontFamily.getFontList().size());
List<FontConfig.Font> fontList = fontFamily.getFontList();
private FontConfig.FontFamily resolveFontFiles(FontUpdateRequest.Family fontFamily) {
List<FontUpdateRequest.Font> fontList = fontFamily.getFonts();
List<FontConfig.Font> 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<String, File> getFontFileMap() {
@@ -566,27 +545,54 @@ final class UpdatableFontDir {
return map;
}
@VisibleForTesting
Map<String, FontConfig.FontFamily> getFontFamilyMap() {
return mFontFamilyMap;
}
/* package */ FontConfig getSystemFontConfig() {
FontConfig config = SystemFonts.getSystemFontConfig(getFontFileMap(), 0, 0);
PersistentSystemFontConfig.Config persistentConfig = readPersistentConfig();
List<FontUpdateRequest.Family> families = persistentConfig.fontFamilies;
List<FontConfig.FontFamily> 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<String, FontConfig.FontFamily> getFontFamilyMap() {
PersistentSystemFontConfig.Config curConfig = readPersistentConfig();
Map<String, FontConfig.FontFamily> 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;
}
}

View File

@@ -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(
"<family name='test'>"
+ " <font>test.ttf</font>"
+ " <font name=\"test\" />"
+ "</family>");
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);
}
}

View File

@@ -699,7 +699,7 @@ public final class UpdatableFontDirTest {
+ " <font>test.ttf</font>"
+ "</family>")));
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<FontUpdateRequest.Font> 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,