Merge "Support font family update internally." into sc-dev

This commit is contained in:
Kohsuke Yatoh
2021-02-10 16:42:11 +00:00
committed by Android (Google) Code Review
5 changed files with 385 additions and 22 deletions

View File

@@ -16,18 +16,33 @@
package android.graphics.fonts;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.text.FontConfig;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Represents a font update request. Currently only font install request is supported.
* @hide
*/
// TODO: Support font config update.
public final class FontUpdateRequest implements Parcelable {
public static final int TYPE_UPDATE_FONT_FILE = 0;
public static final int TYPE_UPDATE_FONT_FAMILY = 1;
@IntDef(prefix = "TYPE_", value = {
TYPE_UPDATE_FONT_FILE,
TYPE_UPDATE_FONT_FAMILY,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
public static final Creator<FontUpdateRequest> CREATOR = new Creator<FontUpdateRequest>() {
@Override
public FontUpdateRequest createFromParcel(Parcel in) {
@@ -40,39 +55,67 @@ public final class FontUpdateRequest implements Parcelable {
}
};
@NonNull
private final @Type int mType;
// NonNull if mType == TYPE_UPDATE_FONT_FILE.
@Nullable
private final ParcelFileDescriptor mFd;
@NonNull
// NonNull if mType == TYPE_UPDATE_FONT_FILE.
@Nullable
private final byte[] mSignature;
// NonNull if mType == TYPE_UPDATE_FONT_FAMILY.
@Nullable
private final FontConfig.FontFamily mFontFamily;
public FontUpdateRequest(@NonNull ParcelFileDescriptor fd, @NonNull byte[] signature) {
mType = TYPE_UPDATE_FONT_FILE;
mFd = fd;
mSignature = signature;
mFontFamily = null;
}
private FontUpdateRequest(Parcel in) {
public FontUpdateRequest(@NonNull FontConfig.FontFamily fontFamily) {
mType = TYPE_UPDATE_FONT_FAMILY;
mFd = null;
mSignature = null;
mFontFamily = fontFamily;
}
protected FontUpdateRequest(Parcel in) {
mType = in.readInt();
mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
mSignature = in.readBlob();
mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader());
}
@NonNull
public @Type int getType() {
return mType;
}
@Nullable
public ParcelFileDescriptor getFd() {
return mFd;
}
@NonNull
@Nullable
public byte[] getSignature() {
return mSignature;
}
@Nullable
public FontConfig.FontFamily getFontFamily() {
return mFontFamily;
}
@Override
public int describeContents() {
return Parcelable.CONTENTS_FILE_DESCRIPTOR;
return mFd != null ? mFd.describeContents() : 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeParcelable(mFd, flags);
dest.writeBlob(mSignature);
dest.writeParcelable(mFontFamily, flags);
}
}

View File

@@ -17,6 +17,8 @@
package com.android.server.graphics.fonts;
import android.annotation.NonNull;
import android.graphics.FontListParser;
import android.text.FontConfig;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
@@ -30,6 +32,8 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/* package */ class PersistentSystemFontConfig {
@@ -38,11 +42,13 @@ import java.util.Set;
private static final String TAG_ROOT = "fontConfig";
private static final String TAG_LAST_MODIFIED_DATE = "lastModifiedDate";
private static final String TAG_UPDATED_FONT_DIR = "updatedFontDir";
private static final String TAG_FAMILY = "family";
private static final String ATTR_VALUE = "value";
/* package */ static class Config {
public long lastModifiedDate;
public final Set<String> updatedFontDirs = new ArraySet<>();
public final List<FontConfig.FontFamily> fontFamilies = new ArrayList<>();
}
/**
@@ -72,6 +78,11 @@ import java.util.Set;
case TAG_UPDATED_FONT_DIR:
out.updatedFontDirs.add(getAttribute(parser, ATTR_VALUE));
break;
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 */));
default:
Slog.w(TAG, "Skipping unknown tag: " + tag);
}
@@ -97,6 +108,13 @@ import java.util.Set;
out.attribute(null, ATTR_VALUE, dir);
out.endTag(null, TAG_UPDATED_FONT_DIR);
}
List<FontConfig.FontFamily> fontFamilies = config.fontFamilies;
for (int i = 0; i < fontFamilies.size(); i++) {
FontConfig.FontFamily fontFamily = fontFamilies.get(i);
out.startTag(null, TAG_FAMILY);
FontListParser.writeFamily(out, fontFamily);
out.endTag(null, TAG_FAMILY);
}
out.endTag(null, TAG_ROOT);
out.endDocument();

View File

@@ -31,6 +31,8 @@ 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;
@@ -40,6 +42,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -118,6 +121,12 @@ 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));
@@ -136,6 +145,7 @@ final class UpdatableFontDir {
/* package */ void loadFontFileMap() {
mFontFileInfoMap.clear();
mFontFamilyMap.clear();
mLastModifiedDate = 0;
boolean success = false;
try {
@@ -168,6 +178,13 @@ 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);
// Ignore failures as updated fonts may be obsoleted by system OTA update.
addFontFamily(fontFamily);
}
success = true;
} catch (Throwable t) {
// If something happened during loading system fonts, clear all contents in finally
@@ -177,6 +194,7 @@ final class UpdatableFontDir {
// Delete all files just in case if we find a problematic file.
if (!success) {
mFontFileInfoMap.clear();
mFontFamilyMap.clear();
mLastModifiedDate = 0;
FileUtils.deleteContents(mFilesDir);
}
@@ -186,10 +204,11 @@ final class UpdatableFontDir {
/* package */ void clearUpdates() throws SystemFontException {
mFontFileInfoMap.clear();
FileUtils.deleteContents(mFilesDir);
mFontFamilyMap.clear();
mLastModifiedDate = Instant.now().getEpochSecond();
try (FileOutputStream fos = new FileOutputStream(mConfigFile)) {
PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig());
PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig());
} catch (Exception e) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
@@ -206,17 +225,29 @@ 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);
long backupLastModifiedDate = mLastModifiedDate;
boolean success = false;
try {
for (FontUpdateRequest request : requests) {
installFontFile(request.getFd().getFileDescriptor(), request.getSignature());
switch (request.getType()) {
case FontUpdateRequest.TYPE_UPDATE_FONT_FILE:
installFontFile(
request.getFd().getFileDescriptor(), request.getSignature());
break;
case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY:
// TODO: define error code.
if (!addFontFamily(request.getFontFamily())) {
throw new IllegalArgumentException("Invalid font family");
}
break;
}
}
// Write config file.
mLastModifiedDate = Instant.now().getEpochSecond();
try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) {
PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig());
PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig());
} catch (Exception e) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
@@ -234,6 +265,8 @@ final class UpdatableFontDir {
if (!success) {
mFontFileInfoMap.clear();
mFontFileInfoMap.putAll(backupMap);
mFontFamilyMap.clear();
mFontFamilyMap.putAll(backupFamilies);
mLastModifiedDate = backupLastModifiedDate;
}
}
@@ -454,12 +487,52 @@ final class UpdatableFontDir {
}
}
private PersistentSystemFontConfig.Config getPersistentConfig() {
/**
* 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 boolean addFontFamily(FontConfig.FontFamily fontFamily) {
if (fontFamily.getName() == null) {
Slog.e(TAG, "Name is null.");
return false;
}
FontConfig.FontFamily resolvedFontFamily = resolveFontFiles(fontFamily);
if (resolvedFontFamily == null) {
Slog.e(TAG, "Required fonts are not available");
return false;
}
mFontFamilyMap.put(resolvedFontFamily.getName(), resolvedFontFamily);
return true;
}
@Nullable
private FontConfig.FontFamily resolveFontFiles(FontConfig.FontFamily fontFamily) {
List<FontConfig.Font> resolvedFonts = new ArrayList<>(fontFamily.getFontList().size());
List<FontConfig.Font> fontList = fontFamily.getFontList();
for (int i = 0; i < fontList.size(); i++) {
FontConfig.Font font = fontList.get(i);
FontFileInfo info = mFontFileInfoMap.get(font.getFile().getName());
if (info == null) {
return null;
}
resolvedFonts.add(new FontConfig.Font(info.mFile, null, font.getStyle(),
font.getTtcIndex(), font.getFontVariationSettings(), font.getFontFamilyName()));
}
return new FontConfig.FontFamily(resolvedFonts, fontFamily.getName(),
fontFamily.getLocaleList(), fontFamily.getVariant());
}
private PersistentSystemFontConfig.Config createPersistentConfig() {
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
config.lastModifiedDate = mLastModifiedDate;
for (FontFileInfo info : mFontFileInfoMap.values()) {
config.updatedFontDirs.add(info.getRandomizedFontDir().getName());
}
config.fontFamilies.addAll(mFontFamilyMap.values());
return config;
}
@@ -471,8 +544,24 @@ final class UpdatableFontDir {
return map;
}
@VisibleForTesting
Map<String, FontConfig.FontFamily> getFontFamilyMap() {
return mFontFamilyMap;
}
/* package */ FontConfig getSystemFontConfig() {
return SystemFonts.getSystemFontConfig(getFontFileMap(), mLastModifiedDate, mConfigVersion);
FontConfig config = SystemFonts.getSystemFontConfig(getFontFileMap(), 0, 0);
List<FontConfig.FontFamily> mergedFamilies =
new ArrayList<>(config.getFontFamilies().size() + mFontFamilyMap.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());
return new FontConfig(
mergedFamilies, config.getAliases(), mLastModifiedDate, mConfigVersion);
}
/* package */ int getConfigVersion() {

View File

@@ -18,13 +18,17 @@ package com.android.server.graphics.fonts;
import static com.google.common.truth.Truth.assertThat;
import android.graphics.FontListParser;
import android.platform.test.annotations.Presubmit;
import android.text.FontConfig;
import android.util.Xml;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
@@ -38,13 +42,19 @@ import java.nio.charset.StandardCharsets;
public final class PersistentSystemFontConfigTest {
@Test
public void testWriteRead() throws IOException, XmlPullParserException {
public void testWriteRead() throws Exception {
long expectedModifiedDate = 1234567890;
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
config.lastModifiedDate = expectedModifiedDate;
config.updatedFontDirs.add("~~abc");
config.updatedFontDirs.add("~~def");
FontConfig.FontFamily fontFamily = parseFontFamily(
"<family name='test'>"
+ " <font>test.ttf</font>"
+ "</family>");
config.fontFamilies.add(fontFamily);
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
PersistentSystemFontConfig.writeToXml(baos, config);
@@ -57,6 +67,7 @@ public final class PersistentSystemFontConfigTest {
assertThat(another.lastModifiedDate).isEqualTo(expectedModifiedDate);
assertThat(another.updatedFontDirs).containsExactly("~~abc", "~~def");
assertThat(another.fontFamilies).containsExactly(fontFamily);
}
}
}
@@ -75,4 +86,11 @@ public final class PersistentSystemFontConfigTest {
}
}
private static FontConfig.FontFamily 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);
}
}

View File

@@ -22,12 +22,15 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
import android.content.Context;
import android.graphics.FontListParser;
import android.graphics.fonts.FontManager;
import android.graphics.fonts.FontUpdateRequest;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.Presubmit;
import android.system.Os;
import android.text.FontConfig;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -37,7 +40,9 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -49,6 +54,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Presubmit
@SmallTest
@@ -157,7 +163,11 @@ public final class UpdatableFontDirTest {
newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
newAddFontFamilyRequest("<family name='foobar'>"
+ " <font>foo.ttf</font>"
+ " <font>bar.ttf</font>"
+ "</family>")));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
@@ -173,6 +183,14 @@ public final class UpdatableFontDirTest {
assertThat(parser.getRevision(dir.getFontFileMap().get("bar.ttf"))).isEqualTo(4);
// Outdated font dir should be deleted.
assertThat(mUpdatableFontFilesDir.list()).hasLength(2);
assertNamedFamilyExists(dir.getSystemFontConfig(), "foobar");
assertThat(dir.getFontFamilyMap()).containsKey("foobar");
FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar");
assertThat(foobar.getFontList()).hasSize(2);
assertThat(foobar.getFontList().get(0).getFile())
.isEqualTo(dir.getFontFileMap().get("foo.ttf"));
assertThat(foobar.getFontList().get(1).getFile())
.isEqualTo(dir.getFontFileMap().get("bar.ttf"));
}
@Test
@@ -184,6 +202,7 @@ public final class UpdatableFontDirTest {
mConfigFile);
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
assertThat(dir.getFontFamilyMap()).isEmpty();
}
@Test
@@ -198,7 +217,11 @@ public final class UpdatableFontDirTest {
newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
newAddFontFamilyRequest("<family name='foobar'>"
+ " <font>foo.ttf</font>"
+ " <font>bar.ttf</font>"
+ "</family>")));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
@@ -211,6 +234,7 @@ public final class UpdatableFontDirTest {
assertThat(dir.getFontFileMap()).isEmpty();
// All font dirs (including dir for "bar.ttf") should be deleted.
assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
assertThat(dir.getFontFamilyMap()).isEmpty();
}
@Test
@@ -225,7 +249,11 @@ public final class UpdatableFontDirTest {
newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
newAddFontFamilyRequest("<family name='foobar'>"
+ " <font>foo.ttf</font>"
+ " <font>bar.ttf</font>"
+ "</family>")));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
@@ -239,6 +267,7 @@ public final class UpdatableFontDirTest {
assertThat(dir.getFontFileMap()).isEmpty();
// All font dirs (including dir for "bar.ttf") should be deleted.
assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
assertThat(dir.getFontFamilyMap()).isEmpty();
}
@Test
@@ -253,7 +282,11 @@ public final class UpdatableFontDirTest {
newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
newAddFontFamilyRequest("<family name='foobar'>"
+ " <font>foo.ttf</font>"
+ " <font>bar.ttf</font>"
+ "</family>")));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
@@ -274,6 +307,8 @@ public final class UpdatableFontDirTest {
// We don't delete bar.ttf in this case, because it's normal that OTA updates preinstalled
// fonts.
assertThat(mUpdatableFontFilesDir.list()).hasLength(1);
// Font family depending on obsoleted font should be removed.
assertThat(dir.getFontFamilyMap()).isEmpty();
}
@Test
@@ -285,6 +320,7 @@ public final class UpdatableFontDirTest {
new File("/dev/null"));
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
assertThat(dir.getFontFamilyMap()).isEmpty();
}
@Test
@@ -295,12 +331,19 @@ public final class UpdatableFontDirTest {
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
dirForPreparation.loadFontFileMap();
dirForPreparation.update(
Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
newAddFontFamilyRequest("<family name='foobar'>"
+ " <font>foo.ttf</font>"
+ "</family>")));
try {
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo,2", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", "Invalid signature")));
newFontUpdateRequest("bar,2", "Invalid signature"),
newAddFontFamilyRequest("<family name='foobar'>"
+ " <font>foo.ttf</font>"
+ " <font>bar.ttf</font>"
+ "</family>")));
fail("Batch update with invalid signature should fail");
} catch (FontManagerService.SystemFontException e) {
// Expected
@@ -313,6 +356,11 @@ public final class UpdatableFontDirTest {
// The state should be rolled back as a whole if one of the update requests fail.
assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
assertThat(dir.getFontFamilyMap()).containsKey("foobar");
FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar");
assertThat(foobar.getFontList()).hasSize(1);
assertThat(foobar.getFontList().get(0).getFile())
.isEqualTo(dir.getFontFileMap().get("foo.ttf"));
}
@Test
@@ -364,7 +412,7 @@ public final class UpdatableFontDirTest {
dir.update(Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE)));
try {
dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
fail("Expect IllegalArgumentException");
fail("Expect SystemFontException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
}
@@ -440,7 +488,7 @@ public final class UpdatableFontDirTest {
try {
dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
fail("Expect IllegalArgumentException");
fail("Expect SystemFontException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
}
@@ -599,6 +647,127 @@ public final class UpdatableFontDirTest {
assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
}
@Test
public void addFontFamily() throws Exception {
FakeFontFileParser parser = new FakeFontFileParser();
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
dir.loadFontFileMap();
dir.update(Arrays.asList(
newFontUpdateRequest("test,1", GOOD_SIGNATURE),
newAddFontFamilyRequest("<family name='test'>"
+ " <font>test.ttf</font>"
+ "</family>")));
assertThat(dir.getFontFileMap()).containsKey("test.ttf");
assertThat(dir.getFontFamilyMap()).containsKey("test");
FontConfig.FontFamily test = dir.getFontFamilyMap().get("test");
assertThat(test.getFontList()).hasSize(1);
assertThat(test.getFontList().get(0).getFile())
.isEqualTo(dir.getFontFileMap().get("test.ttf"));
}
@Test
public void addFontFamily_noName() throws Exception {
FakeFontFileParser parser = new FakeFontFileParser();
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
dir.loadFontFileMap();
try {
dir.update(Arrays.asList(
newFontUpdateRequest("test,1", GOOD_SIGNATURE),
newAddFontFamilyRequest("<family lang='en'>"
+ " <font>test.ttf</font>"
+ "</family>")));
fail("Expect IllegalArgumentException");
} catch (IllegalArgumentException e) {
// Expect
}
}
@Test
public void addFontFamily_fontNotAvailable() throws Exception {
FakeFontFileParser parser = new FakeFontFileParser();
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
dir.loadFontFileMap();
try {
dir.update(Arrays.asList(newAddFontFamilyRequest("<family name='test'>"
+ " <font>test.ttf</font>"
+ "</family>")));
fail("Expect IllegalArgumentException");
} catch (IllegalArgumentException e) {
// Expect
}
}
@Test
public void getSystemFontConfig() throws Exception {
FakeFontFileParser parser = new FakeFontFileParser();
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
dir.loadFontFileMap();
// We assume we have monospace.
assertNamedFamilyExists(dir.getSystemFontConfig(), "monospace");
dir.update(Arrays.asList(
newFontUpdateRequest("test,1", GOOD_SIGNATURE),
// Updating an existing font family.
newAddFontFamilyRequest("<family name='monospace'>"
+ " <font>test.ttf</font>"
+ "</family>"),
// Adding a new font family.
newAddFontFamilyRequest("<family name='test'>"
+ " <font>test.ttf</font>"
+ "</family>")));
FontConfig fontConfig = dir.getSystemFontConfig();
assertNamedFamilyExists(fontConfig, "monospace");
FontConfig.FontFamily monospace = getLastFamily(fontConfig, "monospace");
assertThat(monospace.getFontList()).hasSize(1);
assertThat(monospace.getFontList().get(0).getFile())
.isEqualTo(dir.getFontFileMap().get("test.ttf"));
assertNamedFamilyExists(fontConfig, "test");
assertThat(getLastFamily(fontConfig, "test").getFontList())
.isEqualTo(monospace.getFontList());
}
@Test
public void getSystemFontConfig_preserveFirstFontFamily() throws Exception {
FakeFontFileParser parser = new FakeFontFileParser();
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
dir.loadFontFileMap();
assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0);
assertThat(firstFontFamily.getName()).isNotEmpty();
dir.update(Arrays.asList(
newFontUpdateRequest("test,1", GOOD_SIGNATURE),
newAddFontFamilyRequest("<family name='" + firstFontFamily.getName() + "'>"
+ " <font>test.ttf</font>"
+ "</family>")));
FontConfig fontConfig = dir.getSystemFontConfig();
assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
assertThat(fontConfig.getFontFamilies().get(0)).isEqualTo(firstFontFamily);
FontConfig.FontFamily updated = getLastFamily(fontConfig, firstFontFamily.getName());
assertThat(updated.getFontList()).hasSize(1);
assertThat(updated.getFontList().get(0).getFile())
.isEqualTo(dir.getFontFileMap().get("test.ttf"));
assertThat(updated).isNotEqualTo(firstFontFamily);
}
private FontUpdateRequest newFontUpdateRequest(String content, String signature)
throws Exception {
File file = File.createTempFile("font", "ttf", mCacheDir);
@@ -608,10 +777,36 @@ public final class UpdatableFontDirTest {
signature.getBytes());
}
private static FontUpdateRequest newAddFontFamilyRequest(String xml) throws Exception {
XmlPullParser parser = Xml.newPullParser();
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);
}
private void writeConfig(PersistentSystemFontConfig.Config config,
File file) throws IOException {
try (FileOutputStream fos = new FileOutputStream(file)) {
PersistentSystemFontConfig.writeToXml(fos, config);
}
}
// Returns the last family with the given name, which will be used for creating Typeface.
private static FontConfig.FontFamily getLastFamily(FontConfig fontConfig, String familyName) {
List<FontConfig.FontFamily> fontFamilies = fontConfig.getFontFamilies();
for (int i = fontFamilies.size() - 1; i >= 0; i--) {
if (familyName.equals(fontFamilies.get(i).getName())) {
return fontFamilies.get(i);
}
}
return null;
}
private static void assertNamedFamilyExists(FontConfig fontConfig, String familyName) {
assertThat(fontConfig.getFontFamilies().stream()
.map(FontConfig.FontFamily::getName)
.collect(Collectors.toSet())).contains(familyName);
}
}