Merge "Use separate XML for update config in system" into sc-dev

This commit is contained in:
Seigo Nonaka
2021-03-31 01:15:44 +00:00
committed by Android (Google) Code Review
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,