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:
@@ -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;
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
package android.graphics.fonts;
|
||||
|
||||
/** @hide */
|
||||
parcelable SystemFontState;
|
||||
parcelable FontUpdateRequest;
|
||||
78
core/java/android/graphics/fonts/FontUpdateRequest.java
Normal file
78
core/java/android/graphics/fonts/FontUpdateRequest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user