diff options
| author | 2021-02-10 16:42:11 +0000 | |
|---|---|---|
| committer | 2021-02-10 16:42:11 +0000 | |
| commit | 7a6f5495bd1cf5b33498b3a86beb19cb420d0482 (patch) | |
| tree | c96c23ee9b23c900517002c7a8a6faf9753b84ef | |
| parent | 58e343079e2059dc8697aebbdfe73dbc4d7d73c3 (diff) | |
| parent | 9aa5c97a516652646b4ad2b30823c9b180eabbe6 (diff) | |
Merge "Support font family update internally." into sc-dev
5 files changed, 385 insertions, 22 deletions
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java index db047f8ba696..f551d6a175da 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 @@ 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; + } + + public FontUpdateRequest(@NonNull FontConfig.FontFamily fontFamily) { + mType = TYPE_UPDATE_FONT_FAMILY; + mFd = null; + mSignature = null; + mFontFamily = fontFamily; } - private FontUpdateRequest(Parcel in) { + 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 017f11ceaf06..d514aab31e8d 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 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(); 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 dac94f6aa9d2..45f2a38b6773 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.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() { 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 86054e48fe6d..27fce3c37fd9 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 @@ 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); + } } 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 cb83b0f7f2f7..4bbf96fcb7ef 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 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); + } } |