Support font family update internally.
Bug: 179103383 Test: atest FrameworksServicesTests:UpdatableFontDirTest Change-Id: I053a04afd0f83f015289f2883ebade4cc44ff7c4
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user