Support multi font update internally.

This CL make UpdatableFontDir support multi font update in transaction.
The public / shell API is TBD.

Bug: 179103383
Test: atest CtsGraphicsTestCases:FontManagerTest
Test: atest FrameworksServicesTests:PersistentSystemFontConfigTest
Test: atest FrameworksServicesTests:UpdatableFontDirTest
Test: atest UpdatableSystemFontTest
Change-Id: If9474a8ab81fe194b2d76080a4b066131fcd9e44
This commit is contained in:
Kohsuke Yatoh
2021-02-02 16:43:09 -08:00
parent 944681eda5
commit f57788aaa5
10 changed files with 324 additions and 138 deletions

View File

@@ -261,7 +261,7 @@ public class FontManager {
@IntRange(from = 0) int baseVersion
) {
try {
return mIFontManager.updateFont(pfd, signature, baseVersion);
return mIFontManager.updateFont(baseVersion, new FontUpdateRequest(pfd, signature));
} catch (RemoteException e) {
Log.e(TAG, "Failed to call updateFont API", e);
return RESULT_ERROR_REMOTE_EXCEPTION;

View File

@@ -17,4 +17,4 @@
package android.graphics.fonts;
/** @hide */
parcelable SystemFontState;
parcelable FontUpdateRequest;

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.graphics.fonts;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
/**
* 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 Creator<FontUpdateRequest> CREATOR = new Creator<FontUpdateRequest>() {
@Override
public FontUpdateRequest createFromParcel(Parcel in) {
return new FontUpdateRequest(in);
}
@Override
public FontUpdateRequest[] newArray(int size) {
return new FontUpdateRequest[size];
}
};
@NonNull
private final ParcelFileDescriptor mFd;
@NonNull
private final byte[] mSignature;
public FontUpdateRequest(@NonNull ParcelFileDescriptor fd, @NonNull byte[] signature) {
mFd = fd;
mSignature = signature;
}
private FontUpdateRequest(Parcel in) {
mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
mSignature = in.readBlob();
}
@NonNull
public ParcelFileDescriptor getFd() {
return mFd;
}
@NonNull
public byte[] getSignature() {
return mSignature;
}
@Override
public int describeContents() {
return Parcelable.CONTENTS_FILE_DESCRIPTOR;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mFd, flags);
dest.writeBlob(mSignature);
}
}

View File

@@ -17,8 +17,8 @@
package com.android.internal.graphics.fonts;
import android.os.ParcelFileDescriptor;
import android.graphics.fonts.FontUpdateRequest;
import android.text.FontConfig;
import android.graphics.fonts.SystemFontState;
/**
* System private interface for talking with
@@ -28,5 +28,5 @@ import android.graphics.fonts.SystemFontState;
interface IFontManager {
FontConfig getFontConfig();
int updateFont(in ParcelFileDescriptor fd, in byte[] signature, int baseVersion);
int updateFont(int baseVersion, in FontUpdateRequest request);
}

View File

@@ -24,8 +24,8 @@ import android.graphics.Typeface;
import android.graphics.fonts.FontFamily;
import android.graphics.fonts.FontFileUtil;
import android.graphics.fonts.FontManager;
import android.graphics.fonts.FontUpdateRequest;
import android.graphics.fonts.SystemFonts;
import android.os.ParcelFileDescriptor;
import android.os.ResultReceiver;
import android.os.SharedMemory;
import android.os.ShellCallback;
@@ -54,6 +54,7 @@ import java.nio.NioUtils;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -71,14 +72,15 @@ public final class FontManagerService extends IFontManager.Stub {
}
@Override
public int updateFont(ParcelFileDescriptor fd, byte[] signature, int baseVersion) {
Objects.requireNonNull(fd);
Objects.requireNonNull(signature);
public int updateFont(int baseVersion, @NonNull FontUpdateRequest request) {
Objects.requireNonNull(request);
Objects.requireNonNull(request.getFd());
Objects.requireNonNull(request.getSignature());
Preconditions.checkArgumentNonnegative(baseVersion);
getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS,
"UPDATE_FONTS permission required.");
try {
installFontFile(fd.getFileDescriptor(), signature, baseVersion);
update(baseVersion, Collections.singletonList(request));
return FontManager.RESULT_SUCCESS;
} catch (SystemFontException e) {
Slog.e(TAG, "Failed to update font file", e);
@@ -249,7 +251,7 @@ public final class FontManagerService extends IFontManager.Stub {
}
}
/* package */ void installFontFile(FileDescriptor fd, byte[] pkcs7Signature, int baseVersion)
/* package */ void update(int baseVersion, List<FontUpdateRequest> requests)
throws SystemFontException {
if (mUpdatableFontDir == null) {
throw new SystemFontException(
@@ -265,7 +267,7 @@ public final class FontManagerService extends IFontManager.Stub {
"The base config version is older than current.");
}
try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
mUpdatableFontDir.installFontFile(fd, pkcs7Signature);
mUpdatableFontDir.update(requests);
updateSerializedFontMap();
}
}

View File

@@ -24,6 +24,7 @@ import android.content.Context;
import android.graphics.fonts.Font;
import android.graphics.fonts.FontFamily;
import android.graphics.fonts.FontManager;
import android.graphics.fonts.FontUpdateRequest;
import android.graphics.fonts.FontVariationAxis;
import android.graphics.fonts.SystemFonts;
import android.os.Binder;
@@ -44,6 +45,7 @@ import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -315,6 +317,7 @@ public class FontManagerShellCommand extends ShellCommand {
"Signature file argument is required.");
}
// TODO: close fontFd and sigFd.
ParcelFileDescriptor fontFd = shell.openFileForSystem(fontPath, "r");
if (fontFd == null) {
throw new SystemFontException(
@@ -330,29 +333,24 @@ public class FontManagerShellCommand extends ShellCommand {
}
try (FileInputStream sigFis = new FileInputStream(sigFd.getFileDescriptor())) {
try (FileInputStream fontFis = new FileInputStream(fontFd.getFileDescriptor())) {
int len = sigFis.available();
if (len > MAX_SIGNATURE_FILE_SIZE_BYTES) {
throw new SystemFontException(
FontManager.RESULT_ERROR_SIGNATURE_TOO_LARGE,
"Signature file is too large");
}
byte[] signature = new byte[len];
if (sigFis.read(signature, 0, len) != len) {
throw new SystemFontException(
FontManager.RESULT_ERROR_INVALID_SIGNATURE_FILE,
"Invalid read length");
}
mService.installFontFile(fontFis.getFD(), signature, -1);
} catch (IOException e) {
int len = sigFis.available();
if (len > MAX_SIGNATURE_FILE_SIZE_BYTES) {
throw new SystemFontException(
FontManager.RESULT_ERROR_SIGNATURE_TOO_LARGE,
"Signature file is too large");
}
byte[] signature = new byte[len];
if (sigFis.read(signature, 0, len) != len) {
throw new SystemFontException(
FontManager.RESULT_ERROR_INVALID_SIGNATURE_FILE,
"Failed to read signature file.", e);
"Invalid read length");
}
mService.update(
-1, Collections.singletonList(new FontUpdateRequest(fontFd, signature)));
} catch (IOException e) {
throw new SystemFontException(
FontManager.RESULT_ERROR_INVALID_FONT_FILE,
"Failed to read font files.", e);
FontManager.RESULT_ERROR_INVALID_SIGNATURE_FILE,
"Failed to read signature file.", e);
}
shell.getOutPrintWriter().println("Success"); // TODO: Output more details.

View File

@@ -18,6 +18,7 @@ package com.android.server.graphics.fonts;
import android.annotation.NonNull;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
@@ -29,24 +30,19 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Set;
/* package */ class PersistentSystemFontConfig {
private static final String TAG = "PersistentSystemFontConfig";
private static final String TAG_ROOT = "fontConfig";
private static final String TAG_LAST_MODIFIED_DATE = "lastModifiedDate";
private static final String TAG_VALUE = "value";
private static final String TAG_UPDATED_FONT_DIR = "updatedFontDir";
private static final String ATTR_VALUE = "value";
/* package */ static class Config {
public long lastModifiedDate;
public void reset() {
lastModifiedDate = 0;
}
public void copyTo(@NonNull Config out) {
out.lastModifiedDate = lastModifiedDate;
}
public final Set<String> updatedFontDirs = new ArraySet<>();
}
/**
@@ -54,7 +50,6 @@ import java.io.OutputStream;
*/
public static void loadFromXml(@NonNull InputStream is, @NonNull Config out)
throws XmlPullParserException, IOException {
out.reset();
TypedXmlPullParser parser = Xml.resolvePullParser(is);
int type;
@@ -72,7 +67,10 @@ import java.io.OutputStream;
} else if (depth == 2) {
switch (tag) {
case TAG_LAST_MODIFIED_DATE:
out.lastModifiedDate = parseLongAttribute(parser, TAG_VALUE, 0);
out.lastModifiedDate = parseLongAttribute(parser, ATTR_VALUE, 0);
break;
case TAG_UPDATED_FONT_DIR:
out.updatedFontDirs.add(getAttribute(parser, ATTR_VALUE));
break;
default:
Slog.w(TAG, "Skipping unknown tag: " + tag);
@@ -92,8 +90,13 @@ import java.io.OutputStream;
out.startTag(null, TAG_ROOT);
out.startTag(null, TAG_LAST_MODIFIED_DATE);
out.attribute(null, TAG_VALUE, Long.toString(config.lastModifiedDate));
out.attribute(null, ATTR_VALUE, Long.toString(config.lastModifiedDate));
out.endTag(null, TAG_LAST_MODIFIED_DATE);
for (String dir : config.updatedFontDirs) {
out.startTag(null, TAG_UPDATED_FONT_DIR);
out.attribute(null, ATTR_VALUE, dir);
out.endTag(null, TAG_UPDATED_FONT_DIR);
}
out.endTag(null, TAG_ROOT);
out.endDocument();
@@ -111,4 +114,9 @@ import java.io.OutputStream;
}
}
@NonNull
private static String getAttribute(TypedXmlPullParser parser, String attr) {
final String value = parser.getAttributeValue(null /* namespace */, attr);
return value == null ? "" : value;
}
}

View File

@@ -21,6 +21,7 @@ import static com.android.server.graphics.fonts.FontManagerService.SystemFontExc
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.fonts.FontManager;
import android.graphics.fonts.FontUpdateRequest;
import android.graphics.fonts.SystemFonts;
import android.os.FileUtils;
import android.system.ErrnoException;
@@ -72,15 +73,6 @@ final class UpdatableFontDir {
boolean rename(File src, File dest);
}
/** Interface to mock persistent configuration */
interface PersistentConfig {
void loadFromXml(PersistentSystemFontConfig.Config out)
throws XmlPullParserException, IOException;
void writeToXml(PersistentSystemFontConfig.Config config)
throws IOException;
boolean rename(File src, File dest);
}
/** Data class to hold font file path and revision. */
private static final class FontFileInfo {
private final File mFile;
@@ -116,9 +108,7 @@ final class UpdatableFontDir {
private final File mConfigFile;
private final File mTmpConfigFile;
private final PersistentSystemFontConfig.Config mConfig =
new PersistentSystemFontConfig.Config();
private long mLastModifiedDate;
private int mConfigVersion = 1;
/**
@@ -145,22 +135,36 @@ final class UpdatableFontDir {
}
/* package */ void loadFontFileMap() {
boolean success = false;
try (FileInputStream fis = new FileInputStream(mConfigFile)) {
PersistentSystemFontConfig.loadFromXml(fis, mConfig);
} catch (IOException | XmlPullParserException e) {
mConfig.reset();
}
mFontFileInfoMap.clear();
mLastModifiedDate = 0;
boolean success = false;
try {
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
try (FileInputStream fis = new FileInputStream(mConfigFile)) {
PersistentSystemFontConfig.loadFromXml(fis, config);
} catch (IOException | XmlPullParserException e) {
Slog.e(TAG, "Failed to load config xml file", e);
return;
}
mLastModifiedDate = config.lastModifiedDate;
File[] dirs = mFilesDir.listFiles();
if (dirs == null) return;
for (File dir : dirs) {
if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) return;
if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) {
Slog.e(TAG, "Unexpected dir found: " + dir);
return;
}
if (!config.updatedFontDirs.contains(dir.getName())) {
Slog.i(TAG, "Deleting obsolete dir: " + dir);
FileUtils.deleteContentsAndDir(dir);
continue;
}
File[] files = dir.listFiles();
if (files == null || files.length != 1) return;
if (files == null || files.length != 1) {
Slog.e(TAG, "Unexpected files in dir: " + dir);
return;
}
FontFileInfo fontFileInfo = validateFontFile(files[0]);
addFileToMapIfNewer(fontFileInfo, true /* deleteOldFile */);
}
@@ -173,6 +177,7 @@ final class UpdatableFontDir {
// Delete all files just in case if we find a problematic file.
if (!success) {
mFontFileInfoMap.clear();
mLastModifiedDate = 0;
FileUtils.deleteContents(mFilesDir);
}
}
@@ -182,10 +187,9 @@ final class UpdatableFontDir {
mFontFileInfoMap.clear();
FileUtils.deleteContents(mFilesDir);
mConfig.reset();
mConfig.lastModifiedDate = Instant.now().getEpochSecond();
mLastModifiedDate = Instant.now().getEpochSecond();
try (FileOutputStream fos = new FileOutputStream(mConfigFile)) {
PersistentSystemFontConfig.writeToXml(fos, mConfig);
PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig());
} catch (Exception e) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
@@ -194,6 +198,47 @@ final class UpdatableFontDir {
mConfigVersion++;
}
/**
* Applies multiple {@link FontUpdateRequest}s in transaction.
* If one of the request fails, the fonts and config are rolled back to the previous state
* before this method is called.
*/
public void update(List<FontUpdateRequest> requests) throws SystemFontException {
// Backup the mapping for rollback.
HashMap<String, FontFileInfo> backupMap = new HashMap<>(mFontFileInfoMap);
long backupLastModifiedDate = mLastModifiedDate;
boolean success = false;
try {
for (FontUpdateRequest request : requests) {
installFontFile(request.getFd().getFileDescriptor(), request.getSignature());
}
// Write config file.
mLastModifiedDate = Instant.now().getEpochSecond();
try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) {
PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig());
} catch (Exception e) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
"Failed to write config XML.", e);
}
if (!mFsverityUtil.rename(mTmpConfigFile, mConfigFile)) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
"Failed to stage the config file.");
}
mConfigVersion++;
success = true;
} finally {
if (!success) {
mFontFileInfoMap.clear();
mFontFileInfoMap.putAll(backupMap);
mLastModifiedDate = backupLastModifiedDate;
}
}
}
/**
* Installs a new font file, or updates an existing font file.
*
@@ -205,7 +250,8 @@ final class UpdatableFontDir {
* @param pkcs7Signature A PKCS#7 detached signature to enable fs-verity for the font file.
* @throws SystemFontException if error occurs.
*/
void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) throws SystemFontException {
private void installFontFile(FileDescriptor fd, byte[] pkcs7Signature)
throws SystemFontException {
File newDir = getRandomDir(mFilesDir);
if (!newDir.mkdir()) {
throw new SystemFontException(
@@ -268,42 +314,11 @@ final class UpdatableFontDir {
"Failed to change mode to 711", e);
}
FontFileInfo fontFileInfo = validateFontFile(newFontFile);
// Write config file.
PersistentSystemFontConfig.Config copied = new PersistentSystemFontConfig.Config();
mConfig.copyTo(copied);
copied.lastModifiedDate = Instant.now().getEpochSecond();
try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) {
PersistentSystemFontConfig.writeToXml(fos, copied);
} catch (Exception e) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
"Failed to write config XML.", e);
}
// Backup the mapping for rollback.
HashMap<String, FontFileInfo> backup = new HashMap<>(mFontFileInfoMap);
if (!addFileToMapIfNewer(fontFileInfo, false)) {
throw new SystemFontException(
FontManager.RESULT_ERROR_DOWNGRADING,
"Downgrading font file is forbidden.");
}
if (!mFsverityUtil.rename(mTmpConfigFile, mConfigFile)) {
// If we fail to stage the config file, need to rollback the config.
mFontFileInfoMap.clear();
mFontFileInfoMap.putAll(backup);
throw new SystemFontException(
FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
"Failed to stage the config file.");
}
// Now font update is succeeded. Update config version.
copied.copyTo(mConfig);
mConfigVersion++;
success = true;
} finally {
if (!success) {
@@ -439,6 +454,15 @@ final class UpdatableFontDir {
}
}
private PersistentSystemFontConfig.Config getPersistentConfig() {
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
config.lastModifiedDate = mLastModifiedDate;
for (FontFileInfo info : mFontFileInfoMap.values()) {
config.updatedFontDirs.add(info.getRandomizedFontDir().getName());
}
return config;
}
Map<String, File> getFontFileMap() {
Map<String, File> map = new HashMap<>();
for (Map.Entry<String, FontFileInfo> entry : mFontFileInfoMap.entrySet()) {
@@ -448,11 +472,7 @@ final class UpdatableFontDir {
}
/* package */ FontConfig getSystemFontConfig() {
return SystemFonts.getSystemFontConfig(
getFontFileMap(),
mConfig.lastModifiedDate,
mConfigVersion
);
return SystemFonts.getSystemFontConfig(getFontFileMap(), mLastModifiedDate, mConfigVersion);
}
/* package */ int getConfigVersion() {

View File

@@ -42,6 +42,8 @@ public final class PersistentSystemFontConfigTest {
long expectedModifiedDate = 1234567890;
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
config.lastModifiedDate = expectedModifiedDate;
config.updatedFontDirs.add("~~abc");
config.updatedFontDirs.add("~~def");
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
PersistentSystemFontConfig.writeToXml(baos, config);
@@ -54,6 +56,7 @@ public final class PersistentSystemFontConfigTest {
PersistentSystemFontConfig.loadFromXml(bais, another);
assertThat(another.lastModifiedDate).isEqualTo(expectedModifiedDate);
assertThat(another.updatedFontDirs).containsExactly("~~abc", "~~def");
}
}
}

View File

@@ -23,7 +23,9 @@ import static org.junit.Assert.fail;
import android.content.Context;
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;
@@ -37,11 +39,12 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -150,13 +153,13 @@ public final class UpdatableFontDirTest {
dirForPreparation.loadFontFileMap();
assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
.isEqualTo(expectedModifiedDate);
installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE);
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
//
assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
.isNotEqualTo(expectedModifiedDate);
@@ -191,10 +194,11 @@ public final class UpdatableFontDirTest {
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
dirForPreparation.loadFontFileMap();
installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE);
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
@@ -217,10 +221,11 @@ public final class UpdatableFontDirTest {
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
dirForPreparation.loadFontFileMap();
installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE);
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
@@ -244,10 +249,11 @@ public final class UpdatableFontDirTest {
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
dirForPreparation.loadFontFileMap();
installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE);
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
@@ -281,6 +287,34 @@ public final class UpdatableFontDirTest {
assertThat(dir.getFontFileMap()).isEmpty();
}
@Test
public void construct_afterBatchFailure() throws Exception {
FakeFontFileParser parser = new FakeFontFileParser();
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
dirForPreparation.loadFontFileMap();
dirForPreparation.update(
Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
try {
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo,2", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", "Invalid signature")));
fail("Batch update with invalid signature should fail");
} catch (FontManagerService.SystemFontException e) {
// Expected
}
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
dir.loadFontFileMap();
// 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);
}
@Test
public void installFontFile() throws Exception {
FakeFontFileParser parser = new FakeFontFileParser();
@@ -290,7 +324,7 @@ public final class UpdatableFontDirTest {
mConfigFile);
dir.loadFontFileMap();
installFontFile(dir, "test,1", GOOD_SIGNATURE);
dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
assertThat(dir.getFontFileMap()).containsKey("test.ttf");
assertThat(parser.getRevision(dir.getFontFileMap().get("test.ttf"))).isEqualTo(1);
File fontFile = dir.getFontFileMap().get("test.ttf");
@@ -308,9 +342,9 @@ public final class UpdatableFontDirTest {
mConfigFile);
dir.loadFontFileMap();
installFontFile(dir, "test,1", GOOD_SIGNATURE);
dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
Map<String, File> mapBeforeUpgrade = dir.getFontFileMap();
installFontFile(dir, "test,2", GOOD_SIGNATURE);
dir.update(Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE)));
assertThat(dir.getFontFileMap()).containsKey("test.ttf");
assertThat(parser.getRevision(dir.getFontFileMap().get("test.ttf"))).isEqualTo(2);
assertThat(mapBeforeUpgrade).containsKey("test.ttf");
@@ -327,9 +361,9 @@ public final class UpdatableFontDirTest {
mConfigFile);
dir.loadFontFileMap();
installFontFile(dir, "test,2", GOOD_SIGNATURE);
dir.update(Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE)));
try {
installFontFile(dir, "test,1", GOOD_SIGNATURE);
dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
fail("Expect IllegalArgumentException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
@@ -348,8 +382,26 @@ public final class UpdatableFontDirTest {
mConfigFile);
dir.loadFontFileMap();
installFontFile(dir, "foo,1", GOOD_SIGNATURE);
installFontFile(dir, "bar,2", GOOD_SIGNATURE);
dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
dir.update(Collections.singletonList(newFontUpdateRequest("bar,2", GOOD_SIGNATURE)));
assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
assertThat(dir.getFontFileMap()).containsKey("bar.ttf");
assertThat(parser.getRevision(dir.getFontFileMap().get("bar.ttf"))).isEqualTo(2);
}
@Test
public void installFontFile_batch() 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("foo,1", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", GOOD_SIGNATURE)));
assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
assertThat(dir.getFontFileMap()).containsKey("bar.ttf");
@@ -366,7 +418,8 @@ public final class UpdatableFontDirTest {
dir.loadFontFileMap();
try {
installFontFile(dir, "test,1", "Invalid signature");
dir.update(
Collections.singletonList(newFontUpdateRequest("test,1", "Invalid signature")));
fail("Expect SystemFontException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode())
@@ -386,7 +439,7 @@ public final class UpdatableFontDirTest {
dir.loadFontFileMap();
try {
installFontFile(dir, "test,1", GOOD_SIGNATURE);
dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
fail("Expect IllegalArgumentException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
@@ -417,7 +470,8 @@ public final class UpdatableFontDirTest {
dir.loadFontFileMap();
try {
installFontFile(dir, "test,2", GOOD_SIGNATURE);
dir.update(
Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE)));
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode())
.isEqualTo(FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG);
@@ -449,7 +503,7 @@ public final class UpdatableFontDirTest {
dir.loadFontFileMap();
try {
installFontFile(dir, "foo,1", GOOD_SIGNATURE);
dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
fail("Expect SystemFontException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode())
@@ -477,7 +531,7 @@ public final class UpdatableFontDirTest {
dir.loadFontFileMap();
try {
installFontFile(dir, "foo,1", GOOD_SIGNATURE);
dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
fail("Expect SystemFontException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode())
@@ -513,7 +567,7 @@ public final class UpdatableFontDirTest {
dir.loadFontFileMap();
try {
installFontFile(dir, "foo,1", GOOD_SIGNATURE);
dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
fail("Expect SystemFontException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode())
@@ -522,13 +576,36 @@ public final class UpdatableFontDirTest {
assertThat(dir.getFontFileMap()).isEmpty();
}
private void installFontFile(UpdatableFontDir dir, String content, String signature)
@Test
public void installFontFile_batchFailure() throws Exception {
FakeFontFileParser parser = new FakeFontFileParser();
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
dir.loadFontFileMap();
dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
try {
dir.update(Arrays.asList(
newFontUpdateRequest("foo,2", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", "Invalid signature")));
fail("Batch update with invalid signature should fail");
} catch (FontManagerService.SystemFontException e) {
// Expected
}
// 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);
}
private FontUpdateRequest newFontUpdateRequest(String content, String signature)
throws Exception {
File file = File.createTempFile("font", "ttf", mCacheDir);
FileUtils.stringToFile(file, content);
try (FileInputStream in = new FileInputStream(file)) {
dir.installFontFile(in.getFD(), signature.getBytes());
}
return new FontUpdateRequest(
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY),
signature.getBytes());
}
private void writeConfig(PersistentSystemFontConfig.Config config,