Merge "Support font family update internally." into sc-dev am: 7a6f5495bd
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13534852
MUST ONLY BE SUBMITTED BY AUTOMERGER
Change-Id: I8b90a9d4679175e6113f3f4f89f058ac615bf10c
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
index db047f8..f551d6a 100644
--- a/core/java/android/graphics/fonts/FontUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -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 @@
}
};
- @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);
}
}
diff --git a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
index 017f11c..d514aab 100644
--- a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
+++ b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
@@ -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 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 @@
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 @@
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 @@
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();
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index dac94f6..45f2a38 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -31,6 +31,8 @@
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.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 @@
*/
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 @@
/* package */ void loadFontFileMap() {
mFontFileInfoMap.clear();
+ mFontFamilyMap.clear();
mLastModifiedDate = 0;
boolean success = false;
try {
@@ -168,6 +178,13 @@
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 @@
// 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 @@
/* 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 @@
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 @@
if (!success) {
mFontFileInfoMap.clear();
mFontFileInfoMap.putAll(backupMap);
+ mFontFamilyMap.clear();
+ mFontFamilyMap.putAll(backupFamilies);
mLastModifiedDate = backupLastModifiedDate;
}
}
@@ -454,12 +487,52 @@
}
}
- 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 @@
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() {
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
index 86054e4..27fce3c 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
@@ -18,13 +18,17 @@
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 @@
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 @@
assertThat(another.lastModifiedDate).isEqualTo(expectedModifiedDate);
assertThat(another.updatedFontDirs).containsExactly("~~abc", "~~def");
+ assertThat(another.fontFamilies).containsExactly(fontFamily);
}
}
}
@@ -75,4 +86,11 @@
}
}
+ 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);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index cb83b0f..4bbf96f 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -22,12 +22,15 @@
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.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.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
@Presubmit
@SmallTest
@@ -157,7 +163,11 @@
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 @@
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 @@
mConfigFile);
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
+ assertThat(dir.getFontFamilyMap()).isEmpty();
}
@Test
@@ -198,7 +217,11 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
new File("/dev/null"));
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
+ assertThat(dir.getFontFamilyMap()).isEmpty();
}
@Test
@@ -295,12 +331,19 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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);
+ }
}