Merge "Use separate XML for update config in system" into sc-dev
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 <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<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 <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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user