Add more shell command for font
Bug: 173619554 Test: atest UpdatableFontDirTest PersistentSystemFontConfigTest FontManagerTest Change-Id: I25917fa9e8c42742f74222f25c5ba68d96a2c2fd
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.graphics.fonts;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
@@ -28,6 +29,8 @@ import android.util.Log;
|
||||
|
||||
import com.android.internal.graphics.fonts.IFontManager;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -41,6 +44,116 @@ public class FontManager {
|
||||
private static final String TAG = "FontManager";
|
||||
private final @NonNull IFontManager mIFontManager;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = "ERROR_CODE_",
|
||||
value = { ERROR_CODE_OK, ERROR_CODE_FAILED_TO_WRITE_FONT_FILE,
|
||||
ERROR_CODE_VERIFICATION_FAILURE, ERROR_CODE_FONT_NAME_MISMATCH,
|
||||
ERROR_CODE_INVALID_FONT_FILE, ERROR_CODE_MISSING_POST_SCRIPT_NAME,
|
||||
ERROR_CODE_DOWNGRADING, ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE,
|
||||
ERROR_CODE_FONT_UPDATER_DISABLED })
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ErrorCode {}
|
||||
|
||||
/**
|
||||
* Indicates an operation has processed successfully.
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_CODE_OK = 0;
|
||||
|
||||
/**
|
||||
* Indicates a failure of writing font files.
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_CODE_FAILED_TO_WRITE_FONT_FILE = -1;
|
||||
|
||||
/**
|
||||
* Indicates a failure of fs-verity setup.
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_CODE_VERIFICATION_FAILURE = -2;
|
||||
|
||||
/**
|
||||
* Indicates a failure of verifying the font name with PostScript name.
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_CODE_FONT_NAME_MISMATCH = -3;
|
||||
|
||||
/**
|
||||
* Indicates a failure of placing fonts due to unexpected font contents.
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_CODE_INVALID_FONT_FILE = -4;
|
||||
|
||||
/**
|
||||
* Indicates a failure due to missing PostScript name in name table.
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_CODE_MISSING_POST_SCRIPT_NAME = -5;
|
||||
|
||||
/**
|
||||
* Indicates a failure of placing fonts due to downgrading.
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_CODE_DOWNGRADING = -6;
|
||||
|
||||
/**
|
||||
* Indicates a failure of writing system font configuration XML file.
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE = -7;
|
||||
|
||||
/**
|
||||
* Indicates a failure due to disabled font updater.
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_CODE_FONT_UPDATER_DISABLED = -8;
|
||||
|
||||
/**
|
||||
* Indicates a failure of opening font file.
|
||||
*
|
||||
* This error code is only used with the shell command interaction.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_CODE_FAILED_TO_OPEN_FONT_FILE = -10001;
|
||||
|
||||
/**
|
||||
* Indicates a failure of opening signature file.
|
||||
*
|
||||
* This error code is only used with the shell command interaction.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_CODE_FAILED_TO_OPEN_SIGNATURE_FILE = -10002;
|
||||
|
||||
/**
|
||||
* Indicates a failure of invalid shell command arguments.
|
||||
*
|
||||
* This error code is only used with the shell command interaction.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_CODE_INVALID_SHELL_ARGUMENT = -10003;
|
||||
|
||||
/**
|
||||
* Indicates a failure of reading signature file.
|
||||
*
|
||||
* This error code is only used with the shell command interaction.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_CODE_INVALID_SIGNATURE_FILE = -10004;
|
||||
|
||||
/**
|
||||
* Indicates a failure due to exceeding allowed signature file size (8kb).
|
||||
*
|
||||
* This error code is only used with the shell command interaction.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_CODE_SIGNATURE_TOO_LARGE = -10005;
|
||||
|
||||
|
||||
private FontManager(@NonNull IFontManager iFontManager) {
|
||||
mIFontManager = iFontManager;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ import java.util.List;
|
||||
public final class FontConfig implements Parcelable {
|
||||
private final @NonNull List<FontFamily> mFamilies;
|
||||
private final @NonNull List<Alias> mAliases;
|
||||
private final long mLastModifiedDate;
|
||||
private final int mConfigVersion;
|
||||
|
||||
/**
|
||||
* Construct a FontConfig instance.
|
||||
@@ -62,9 +64,12 @@ public final class FontConfig implements Parcelable {
|
||||
*
|
||||
* @hide Only system server can create this instance and passed via IPC.
|
||||
*/
|
||||
public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases) {
|
||||
public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases,
|
||||
long lastModifiedDate, @IntRange(from = 0) int configVersion) {
|
||||
mFamilies = families;
|
||||
mAliases = aliases;
|
||||
mLastModifiedDate = lastModifiedDate;
|
||||
mConfigVersion = configVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,6 +92,26 @@ public final class FontConfig implements Parcelable {
|
||||
return mAliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modified date as Java epoch seconds.
|
||||
*
|
||||
* If there is no update, this return 0.
|
||||
* @hide
|
||||
*/
|
||||
public long getLastModifiedDate() {
|
||||
return mLastModifiedDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the monotonically increasing config version value.
|
||||
*
|
||||
* The config version is reset to 0 when the system is restarted.
|
||||
* @hide
|
||||
*/
|
||||
public @IntRange(from = 0) int getConfigVersion() {
|
||||
return mConfigVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ordered list of families included in the system fonts.
|
||||
* @deprecated Use getFontFamilies instead.
|
||||
@@ -107,6 +132,8 @@ public final class FontConfig implements Parcelable {
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeParcelableList(mFamilies, flags);
|
||||
dest.writeParcelableList(mAliases, flags);
|
||||
dest.writeLong(mLastModifiedDate);
|
||||
dest.writeInt(mConfigVersion);
|
||||
}
|
||||
|
||||
public static final @NonNull Creator<FontConfig> CREATOR = new Creator<FontConfig>() {
|
||||
@@ -116,7 +143,9 @@ public final class FontConfig implements Parcelable {
|
||||
FontFamily.class.getClassLoader());
|
||||
List<Alias> aliases = source.readParcelableList(new ArrayList<>(),
|
||||
Alias.class.getClassLoader());
|
||||
return new FontConfig(families, aliases);
|
||||
long lastModifiedDate = source.readLong();
|
||||
int configVersion = source.readInt();
|
||||
return new FontConfig(families, aliases, lastModifiedDate, configVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -171,7 +171,8 @@ public class TypefaceSystemFallbackTest {
|
||||
FontConfig fontConfig;
|
||||
try {
|
||||
fontConfig = FontListParser.parse(
|
||||
TEST_FONTS_XML, TEST_FONT_DIR, oemXmlPath, TEST_OEM_DIR, updatableFontMap);
|
||||
TEST_FONTS_XML, TEST_FONT_DIR, oemXmlPath, TEST_OEM_DIR, updatableFontMap, 0,
|
||||
0);
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -199,7 +200,7 @@ public class TypefaceSystemFallbackTest {
|
||||
FontConfig fontConfig;
|
||||
try {
|
||||
fontConfig = FontListParser.parse(
|
||||
SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, null, TEST_OEM_DIR, null);
|
||||
SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, null, TEST_OEM_DIR, null, 0, 0);
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class FontFallbackSetup implements AutoCloseable {
|
||||
|
||||
FontConfig fontConfig;
|
||||
try {
|
||||
fontConfig = FontListParser.parse(testFontsXml, mTestFontsDir, null, null, null);
|
||||
fontConfig = FontListParser.parse(testFontsXml, mTestFontsDir, null, null, null, 0, 0);
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ public class FontListParser {
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setInput(in, null);
|
||||
parser.nextTag();
|
||||
return readFamilies(parser, "/system/fonts/", new FontCustomizationParser.Result(), null);
|
||||
return readFamilies(parser, "/system/fonts/", new FontCustomizationParser.Result(), null,
|
||||
0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,7 +72,9 @@ public class FontListParser {
|
||||
@NonNull String systemFontDir,
|
||||
@Nullable String oemCustomizationXmlPath,
|
||||
@Nullable String productFontDir,
|
||||
@Nullable Map<String, File> updatableFontMap
|
||||
@Nullable Map<String, File> updatableFontMap,
|
||||
long lastModifiedDate,
|
||||
int configVersion
|
||||
) throws IOException, XmlPullParserException {
|
||||
FontCustomizationParser.Result oemCustomization;
|
||||
if (oemCustomizationXmlPath != null) {
|
||||
@@ -90,7 +93,8 @@ public class FontListParser {
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setInput(is, null);
|
||||
parser.nextTag();
|
||||
return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap);
|
||||
return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap,
|
||||
lastModifiedDate, configVersion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +102,9 @@ public class FontListParser {
|
||||
@NonNull XmlPullParser parser,
|
||||
@NonNull String fontDir,
|
||||
@NonNull FontCustomizationParser.Result customization,
|
||||
@Nullable Map<String, File> updatableFontMap)
|
||||
@Nullable Map<String, File> updatableFontMap,
|
||||
long lastModifiedDate,
|
||||
int configVersion)
|
||||
throws XmlPullParserException, IOException {
|
||||
List<FontConfig.FontFamily> families = new ArrayList<>();
|
||||
List<FontConfig.Alias> aliases = new ArrayList<>(customization.getAdditionalAliases());
|
||||
@@ -126,7 +132,7 @@ public class FontListParser {
|
||||
}
|
||||
|
||||
families.addAll(oemNamedFamilies.values());
|
||||
return new FontConfig(families, aliases);
|
||||
return new FontConfig(families, aliases, lastModifiedDate, configVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -240,10 +240,12 @@ public final class SystemFonts {
|
||||
* @hide
|
||||
*/
|
||||
public static @NonNull FontConfig getSystemFontConfig(
|
||||
@Nullable Map<String, File> updatableFontMap
|
||||
@Nullable Map<String, File> updatableFontMap,
|
||||
long lastModifiedDate,
|
||||
int configVersion
|
||||
) {
|
||||
return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
|
||||
updatableFontMap);
|
||||
updatableFontMap, lastModifiedDate, configVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,7 +253,8 @@ public final class SystemFonts {
|
||||
* @hide
|
||||
*/
|
||||
public static @NonNull FontConfig getSystemPreinstalledFontConfig() {
|
||||
return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null);
|
||||
return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null,
|
||||
0, 0);
|
||||
}
|
||||
|
||||
/* package */ static @NonNull FontConfig getSystemFontConfigInternal(
|
||||
@@ -259,17 +262,19 @@ public final class SystemFonts {
|
||||
@NonNull String systemFontDir,
|
||||
@Nullable String oemXml,
|
||||
@Nullable String productFontDir,
|
||||
@Nullable Map<String, File> updatableFontMap
|
||||
@Nullable Map<String, File> updatableFontMap,
|
||||
long lastModifiedDate,
|
||||
int configVersion
|
||||
) {
|
||||
try {
|
||||
return FontListParser.parse(fontsXml, systemFontDir, oemXml, productFontDir,
|
||||
updatableFontMap);
|
||||
updatableFontMap, lastModifiedDate, configVersion);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to open/read system font configurations.", e);
|
||||
return new FontConfig(Collections.emptyList(), Collections.emptyList());
|
||||
return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0);
|
||||
} catch (XmlPullParserException e) {
|
||||
Log.e(TAG, "Failed to parse the system font configuration.", e);
|
||||
return new FontConfig(Collections.emptyList(), Collections.emptyList());
|
||||
return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,12 +22,15 @@ import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.fonts.FontFamily;
|
||||
import android.graphics.fonts.FontFileUtil;
|
||||
import android.graphics.fonts.FontManager;
|
||||
import android.graphics.fonts.SystemFonts;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.SharedMemory;
|
||||
import android.os.ShellCallback;
|
||||
import android.system.ErrnoException;
|
||||
import android.text.FontConfig;
|
||||
import android.util.AndroidException;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Slog;
|
||||
|
||||
@@ -48,6 +51,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.NioUtils;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/** A service for managing system fonts. */
|
||||
@@ -55,8 +59,6 @@ import java.util.Map;
|
||||
public final class FontManagerService extends IFontManager.Stub {
|
||||
private static final String TAG = "FontManagerService";
|
||||
|
||||
// TODO: make this a DeviceConfig flag.
|
||||
private static final boolean ENABLE_FONT_UPDATES = false;
|
||||
private static final String FONT_FILES_DIR = "/data/fonts/files";
|
||||
|
||||
@Override
|
||||
@@ -64,6 +66,24 @@ public final class FontManagerService extends IFontManager.Stub {
|
||||
return getCurrentFontSettings().getSystemFontConfig();
|
||||
}
|
||||
|
||||
/* package */ static class SystemFontException extends AndroidException {
|
||||
private final int mErrorCode;
|
||||
|
||||
SystemFontException(@FontManager.ErrorCode int errorCode, String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
mErrorCode = errorCode;
|
||||
}
|
||||
|
||||
SystemFontException(int errorCode, String msg) {
|
||||
super(msg);
|
||||
mErrorCode = errorCode;
|
||||
}
|
||||
|
||||
@FontManager.ErrorCode int getErrorCode() {
|
||||
return mErrorCode;
|
||||
}
|
||||
}
|
||||
|
||||
/** Class to manage FontManagerService's lifecycle. */
|
||||
public static final class Lifecycle extends SystemService {
|
||||
private final FontManagerService mService;
|
||||
@@ -151,7 +171,6 @@ public final class FontManagerService extends IFontManager.Stub {
|
||||
|
||||
@Nullable
|
||||
private static UpdatableFontDir createUpdatableFontDir() {
|
||||
if (!ENABLE_FONT_UPDATES) return null;
|
||||
// If apk verity is supported, fs-verity should be available.
|
||||
if (!FileIntegrityService.isApkVeritySupported()) return null;
|
||||
return new UpdatableFontDir(new File(FONT_FILES_DIR),
|
||||
@@ -178,19 +197,34 @@ public final class FontManagerService extends IFontManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/173619554): Expose as API.
|
||||
private boolean installFontFile(FileDescriptor fd, byte[] pkcs7Signature) {
|
||||
if (mUpdatableFontDir == null) return false;
|
||||
/* package */ void installFontFile(FileDescriptor fd, byte[] pkcs7Signature)
|
||||
throws SystemFontException {
|
||||
if (mUpdatableFontDir == null) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_FONT_UPDATER_DISABLED,
|
||||
"The font updater is disabled.");
|
||||
}
|
||||
synchronized (FontManagerService.this) {
|
||||
try {
|
||||
mUpdatableFontDir.installFontFile(fd, pkcs7Signature);
|
||||
} catch (IOException e) {
|
||||
Slog.w(TAG, "Failed to install font file");
|
||||
return false;
|
||||
}
|
||||
mUpdatableFontDir.installFontFile(fd, pkcs7Signature);
|
||||
// Create updated font map in the next getSerializedSystemFontMap() call.
|
||||
mCurrentFontSettings = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void clearUpdates() throws SystemFontException {
|
||||
if (mUpdatableFontDir == null) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_FONT_UPDATER_DISABLED,
|
||||
"The font updater is disabled.");
|
||||
}
|
||||
mUpdatableFontDir.clearUpdates();
|
||||
}
|
||||
|
||||
/* package */ Map<String, File> getFontFileMap() {
|
||||
if (mUpdatableFontDir == null) {
|
||||
return Collections.emptyMap();
|
||||
} else {
|
||||
return mUpdatableFontDir.getFontFileMap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,11 +236,13 @@ public final class FontManagerService extends IFontManager.Stub {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int handleShellCommand(@NonNull ParcelFileDescriptor in,
|
||||
@NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
|
||||
@NonNull String[] args) {
|
||||
return new FontManagerShellCommand(this).exec(this,
|
||||
in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args);
|
||||
public void onShellCommand(@Nullable FileDescriptor in,
|
||||
@Nullable FileDescriptor out,
|
||||
@Nullable FileDescriptor err,
|
||||
@NonNull String[] args,
|
||||
@Nullable ShellCallback callback,
|
||||
@NonNull ResultReceiver result) throws RemoteException {
|
||||
new FontManagerShellCommand(this).exec(this, in, out, err, args, callback, result);
|
||||
}
|
||||
|
||||
/* package */ static class SystemFontSettings {
|
||||
@@ -245,8 +281,7 @@ public final class FontManagerService extends IFontManager.Stub {
|
||||
public static @Nullable SystemFontSettings create(
|
||||
@Nullable UpdatableFontDir updatableFontDir) {
|
||||
if (updatableFontDir != null) {
|
||||
final FontConfig fontConfig = SystemFonts.getSystemFontConfig(
|
||||
updatableFontDir.getFontFileMap());
|
||||
final FontConfig fontConfig = updatableFontDir.getSystemFontConfig();
|
||||
final Map<String, FontFamily[]> fallback =
|
||||
SystemFonts.buildSystemFallback(fontConfig);
|
||||
final Map<String, Typeface> typefaceMap =
|
||||
@@ -274,4 +309,5 @@ public final class FontManagerService extends IFontManager.Stub {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,19 +16,33 @@
|
||||
|
||||
package com.android.server.graphics.fonts;
|
||||
|
||||
import static com.android.server.graphics.fonts.FontManagerService.SystemFontException;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.graphics.fonts.Font;
|
||||
import android.graphics.fonts.FontFamily;
|
||||
import android.graphics.fonts.FontManager;
|
||||
import android.graphics.fonts.FontVariationAxis;
|
||||
import android.os.Binder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
import android.os.ShellCommand;
|
||||
import android.text.FontConfig;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.util.DumpUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -38,6 +52,13 @@ import java.util.Map;
|
||||
public class FontManagerShellCommand extends ShellCommand {
|
||||
private static final String TAG = "FontManagerShellCommand";
|
||||
|
||||
/**
|
||||
* The maximum size of signature file. This is just to avoid potential abuse.
|
||||
*
|
||||
* This is copied from VerityUtils.java.
|
||||
*/
|
||||
private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192;
|
||||
|
||||
@NonNull private final FontManagerService mService;
|
||||
|
||||
FontManagerShellCommand(@NonNull FontManagerService service) {
|
||||
@@ -46,12 +67,31 @@ public class FontManagerShellCommand extends ShellCommand {
|
||||
|
||||
@Override
|
||||
public int onCommand(String cmd) {
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
|
||||
// Do not change this string since this string is expected in the CTS.
|
||||
getErrPrintWriter().println("Only shell or root user can execute font command.");
|
||||
return 1;
|
||||
}
|
||||
return execCommand(this, cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHelp() {
|
||||
dumpHelp(getOutPrintWriter());
|
||||
PrintWriter w = getOutPrintWriter();
|
||||
w.println("Font service (font) commands");
|
||||
w.println("help");
|
||||
w.println(" Print this help text.");
|
||||
w.println();
|
||||
w.println("dump [family name]");
|
||||
w.println(" Dump all font files in the specified family name.");
|
||||
w.println(" Dump current system font configuration if no family name was specified.");
|
||||
w.println();
|
||||
w.println("update [font file path] [signature file path]");
|
||||
w.println(" Update installed font files with new font file.");
|
||||
w.println();
|
||||
w.println("clear");
|
||||
w.println(" Remove all installed font files and reset to the initial state.");
|
||||
}
|
||||
|
||||
/* package */ void dumpAll(@NonNull IndentingPrintWriter w) {
|
||||
@@ -165,16 +205,6 @@ public class FontManagerShellCommand extends ShellCommand {
|
||||
w.decreaseIndent();
|
||||
}
|
||||
|
||||
private static void dumpHelp(@NonNull PrintWriter w) {
|
||||
w.println("Font service (font) commands");
|
||||
w.println("help");
|
||||
w.println(" Print this help text.");
|
||||
w.println();
|
||||
w.println("dump [family name]");
|
||||
w.println(" Dump all font files in the specified family name.");
|
||||
w.println(" Dump current system font configuration if no family name was specified.");
|
||||
}
|
||||
|
||||
private void dumpFallback(@NonNull IndentingPrintWriter writer,
|
||||
@NonNull FontFamily[] families) {
|
||||
for (FontFamily family : families) {
|
||||
@@ -233,37 +263,143 @@ public class FontManagerShellCommand extends ShellCommand {
|
||||
writer.println(sb.toString());
|
||||
}
|
||||
|
||||
private int execCommand(@NonNull ShellCommand shell, @NonNull String cmd) {
|
||||
private void writeCommandResult(ShellCommand shell, SystemFontException e) {
|
||||
// Print short summary to the stderr.
|
||||
PrintWriter pw = shell.getErrPrintWriter();
|
||||
pw.println(e.getErrorCode());
|
||||
pw.println(e.getMessage());
|
||||
|
||||
// Dump full stack trace to logcat.
|
||||
|
||||
Slog.e(TAG, "Command failed: " + Arrays.toString(shell.getAllArgs()), e);
|
||||
}
|
||||
|
||||
private int dump(ShellCommand shell) {
|
||||
final Context ctx = mService.getContext();
|
||||
final FontManagerService.SystemFontSettings settings =
|
||||
mService.getCurrentFontSettings();
|
||||
if (!DumpUtils.checkDumpPermission(ctx, TAG, shell.getErrPrintWriter())) {
|
||||
return 1;
|
||||
}
|
||||
final IndentingPrintWriter writer =
|
||||
new IndentingPrintWriter(shell.getOutPrintWriter(), " ");
|
||||
String nextArg = shell.getNextArg();
|
||||
if (nextArg == null) {
|
||||
dumpFontConfig(writer, settings.getSystemFontConfig());
|
||||
} else {
|
||||
final Map<String, FontFamily[]> fallbackMap =
|
||||
settings.getSystemFallbackMap();
|
||||
FontFamily[] families = fallbackMap.get(nextArg);
|
||||
if (families == null) {
|
||||
writer.println("Font Family \"" + nextArg + "\" not found");
|
||||
} else {
|
||||
dumpFallback(writer, families);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int update(ShellCommand shell) throws SystemFontException {
|
||||
String fontPath = shell.getNextArg();
|
||||
if (fontPath == null) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_INVALID_SHELL_ARGUMENT,
|
||||
"Font file path argument is required.");
|
||||
}
|
||||
String signaturePath = shell.getNextArg();
|
||||
if (signaturePath == null) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_INVALID_SHELL_ARGUMENT,
|
||||
"Signature file argument is required.");
|
||||
}
|
||||
|
||||
ParcelFileDescriptor fontFd = shell.openFileForSystem(fontPath, "r");
|
||||
if (fontFd == null) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_FAILED_TO_OPEN_FONT_FILE,
|
||||
"Failed to open font file");
|
||||
}
|
||||
|
||||
ParcelFileDescriptor sigFd = shell.openFileForSystem(signaturePath, "r");
|
||||
if (sigFd == null) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_FAILED_TO_OPEN_SIGNATURE_FILE,
|
||||
"Failed to open signature file");
|
||||
}
|
||||
|
||||
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.ERROR_CODE_SIGNATURE_TOO_LARGE,
|
||||
"Signature file is too large");
|
||||
}
|
||||
byte[] signature = new byte[len];
|
||||
if (sigFis.read(signature, 0, len) != len) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_INVALID_SIGNATURE_FILE,
|
||||
"Invalid read length");
|
||||
}
|
||||
mService.installFontFile(fontFis.getFD(), signature);
|
||||
} catch (IOException e) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_INVALID_SIGNATURE_FILE,
|
||||
"Failed to read signature file.", e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_INVALID_FONT_FILE,
|
||||
"Failed to read font files.", e);
|
||||
}
|
||||
|
||||
shell.getOutPrintWriter().println("Success"); // TODO: Output more details.
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int clear(ShellCommand shell) throws SystemFontException {
|
||||
mService.clearUpdates();
|
||||
shell.getOutPrintWriter().println("Success");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int status(ShellCommand shell) throws SystemFontException {
|
||||
final FontManagerService.SystemFontSettings settings = mService.getCurrentFontSettings();
|
||||
final IndentingPrintWriter writer =
|
||||
new IndentingPrintWriter(shell.getOutPrintWriter(), " ");
|
||||
FontConfig config = settings.getSystemFontConfig();
|
||||
|
||||
writer.println("Current Version: " + config.getConfigVersion());
|
||||
LocalDateTime dt = LocalDateTime.ofEpochSecond(config.getLastModifiedDate(), 0,
|
||||
ZoneOffset.UTC);
|
||||
writer.println("Last Modified Date: " + dt.format(DateTimeFormatter.ISO_DATE_TIME));
|
||||
|
||||
Map<String, File> fontFileMap = mService.getFontFileMap();
|
||||
writer.println("Number of updated font files: " + fontFileMap.size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int execCommand(@NonNull ShellCommand shell, @Nullable String cmd) {
|
||||
if (cmd == null) {
|
||||
return shell.handleDefaultCommands(null);
|
||||
}
|
||||
|
||||
final FontManagerService.SystemFontSettings settings = mService.getCurrentFontSettings();
|
||||
|
||||
switch (cmd) {
|
||||
case "dump":
|
||||
if (!DumpUtils.checkDumpPermission(ctx, TAG, shell.getErrPrintWriter())) {
|
||||
return 1;
|
||||
}
|
||||
final IndentingPrintWriter writer =
|
||||
new IndentingPrintWriter(shell.getOutPrintWriter(), " ");
|
||||
String nextArg = shell.getNextArg();
|
||||
if (nextArg == null) {
|
||||
dumpFontConfig(writer, settings.getSystemFontConfig());
|
||||
} else {
|
||||
final Map<String, FontFamily[]> fallbackMap = settings.getSystemFallbackMap();
|
||||
FontFamily[] families = fallbackMap.get(nextArg);
|
||||
if (families == null) {
|
||||
writer.println("Font Family \"" + nextArg + "\" not found");
|
||||
} else {
|
||||
dumpFallback(writer, families);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
default:
|
||||
shell.handleDefaultCommands(cmd);
|
||||
try {
|
||||
switch (cmd) {
|
||||
case "dump":
|
||||
return dump(shell);
|
||||
case "update":
|
||||
return update(shell);
|
||||
case "clear":
|
||||
return clear(shell);
|
||||
case "status":
|
||||
return status(shell);
|
||||
default:
|
||||
return shell.handleDefaultCommands(cmd);
|
||||
}
|
||||
} catch (SystemFontException e) {
|
||||
writeCommandResult(shell, e);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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 com.android.server.graphics.fonts;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Slog;
|
||||
import android.util.TypedXmlPullParser;
|
||||
import android.util.TypedXmlSerializer;
|
||||
import android.util.Xml;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/* 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";
|
||||
|
||||
/* package */ static class Config {
|
||||
public long lastModifiedDate;
|
||||
|
||||
public void reset() {
|
||||
lastModifiedDate = 0;
|
||||
}
|
||||
|
||||
public void copyTo(@NonNull Config out) {
|
||||
out.lastModifiedDate = lastModifiedDate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read config XML and write to out argument.
|
||||
*/
|
||||
public static void loadFromXml(@NonNull InputStream is, @NonNull Config out)
|
||||
throws XmlPullParserException, IOException {
|
||||
out.reset();
|
||||
TypedXmlPullParser parser = Xml.resolvePullParser(is);
|
||||
|
||||
int type;
|
||||
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
|
||||
if (type != XmlPullParser.START_TAG) {
|
||||
continue;
|
||||
}
|
||||
final int depth = parser.getDepth();
|
||||
final String tag = parser.getName();
|
||||
if (depth == 1) {
|
||||
if (!TAG_ROOT.equals(tag)) {
|
||||
Slog.e(TAG, "Invalid root tag: " + tag);
|
||||
return;
|
||||
}
|
||||
} else if (depth == 2) {
|
||||
switch (tag) {
|
||||
case TAG_LAST_MODIFIED_DATE:
|
||||
out.lastModifiedDate = parseLongAttribute(parser, TAG_VALUE, 0);
|
||||
break;
|
||||
default:
|
||||
Slog.w(TAG, "Skipping unknown tag: " + tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Write config to OutputStream as XML file.
|
||||
*/
|
||||
public static void writeToXml(@NonNull OutputStream os, @NonNull Config config)
|
||||
throws IOException {
|
||||
TypedXmlSerializer out = Xml.resolveSerializer(os);
|
||||
out.startDocument(null /* encoding */, true /* standalone */);
|
||||
|
||||
out.startTag(null, TAG_ROOT);
|
||||
out.startTag(null, TAG_LAST_MODIFIED_DATE);
|
||||
out.attribute(null, TAG_VALUE, Long.toString(config.lastModifiedDate));
|
||||
out.endTag(null, TAG_LAST_MODIFIED_DATE);
|
||||
out.endTag(null, TAG_ROOT);
|
||||
|
||||
out.endDocument();
|
||||
}
|
||||
|
||||
private static long parseLongAttribute(TypedXmlPullParser parser, String attr, long defValue) {
|
||||
final String value = parser.getAttributeValue(null /* namespace */, attr);
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
return defValue;
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,18 +16,28 @@
|
||||
|
||||
package com.android.server.graphics.fonts;
|
||||
|
||||
import static com.android.server.graphics.fonts.FontManagerService.SystemFontException;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.fonts.FontManager;
|
||||
import android.graphics.fonts.SystemFonts;
|
||||
import android.os.FileUtils;
|
||||
import android.text.FontConfig;
|
||||
import android.util.Base64;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -39,6 +49,8 @@ final class UpdatableFontDir {
|
||||
// TODO: Support .otf
|
||||
private static final String ALLOWED_EXTENSION = ".ttf";
|
||||
|
||||
private static final String CONFIG_XML_FILE = "/data/fonts/config/config.xml";
|
||||
|
||||
/** Interface to mock font file access in tests. */
|
||||
interface FontFileParser {
|
||||
String getPostScriptName(File file) throws IOException;
|
||||
@@ -55,6 +67,15 @@ 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;
|
||||
@@ -87,6 +108,16 @@ final class UpdatableFontDir {
|
||||
private final List<File> mPreinstalledFontDirs;
|
||||
private final FontFileParser mParser;
|
||||
private final FsverityUtil mFsverityUtil;
|
||||
private final File mConfigFile;
|
||||
private final File mTmpConfigFile;
|
||||
|
||||
@GuardedBy("UpdatableFontDir.this")
|
||||
private final PersistentSystemFontConfig.Config mConfig =
|
||||
new PersistentSystemFontConfig.Config();
|
||||
|
||||
@GuardedBy("UpdatableFontDir.this")
|
||||
private int mConfigVersion = 1;
|
||||
|
||||
/**
|
||||
* A mutable map containing mapping from font file name (e.g. "NotoColorEmoji.ttf") to {@link
|
||||
* FontFileInfo}. All files in this map are validated, and have higher revision numbers than
|
||||
@@ -101,6 +132,20 @@ final class UpdatableFontDir {
|
||||
mPreinstalledFontDirs = preinstalledFontDirs;
|
||||
mParser = parser;
|
||||
mFsverityUtil = fsverityUtil;
|
||||
mConfigFile = new File(CONFIG_XML_FILE);
|
||||
mTmpConfigFile = new File(CONFIG_XML_FILE + ".tmp");
|
||||
loadFontFileMap();
|
||||
}
|
||||
|
||||
// For unit testing
|
||||
UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser,
|
||||
FsverityUtil fsverityUtil, File configFile) {
|
||||
mFilesDir = filesDir;
|
||||
mPreinstalledFontDirs = preinstalledFontDirs;
|
||||
mParser = parser;
|
||||
mFsverityUtil = fsverityUtil;
|
||||
mConfigFile = configFile;
|
||||
mTmpConfigFile = new File(configFile.getAbsoluteFile() + ".tmp");
|
||||
loadFontFileMap();
|
||||
}
|
||||
|
||||
@@ -108,6 +153,13 @@ final class UpdatableFontDir {
|
||||
// TODO: SIGBUS crash protection
|
||||
synchronized (UpdatableFontDir.this) {
|
||||
boolean success = false;
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(mConfigFile)) {
|
||||
PersistentSystemFontConfig.loadFromXml(fis, mConfig);
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
mConfig.reset();
|
||||
}
|
||||
|
||||
mFontFileInfoMap.clear();
|
||||
try {
|
||||
File[] dirs = mFilesDir.listFiles();
|
||||
@@ -117,13 +169,13 @@ final class UpdatableFontDir {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null || files.length != 1) return;
|
||||
FontFileInfo fontFileInfo = validateFontFile(files[0]);
|
||||
if (fontFileInfo == null) {
|
||||
Slog.w(TAG, "Broken file is found. Clearing files.");
|
||||
return;
|
||||
}
|
||||
addFileToMapLocked(fontFileInfo, true /* deleteOldFile */);
|
||||
addFileToMapIfNewerLocked(fontFileInfo, true /* deleteOldFile */);
|
||||
}
|
||||
success = true;
|
||||
} catch (Throwable t) {
|
||||
// If something happened during loading system fonts, clear all contents in finally
|
||||
// block. Here, just dumping errors.
|
||||
Slog.e(TAG, "Failed to load font mappings.", t);
|
||||
} finally {
|
||||
// Delete all files just in case if we find a problematic file.
|
||||
if (!success) {
|
||||
@@ -134,6 +186,24 @@ final class UpdatableFontDir {
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void clearUpdates() throws SystemFontException {
|
||||
synchronized (UpdatableFontDir.this) {
|
||||
mFontFileInfoMap.clear();
|
||||
FileUtils.deleteContents(mFilesDir);
|
||||
|
||||
mConfig.reset();
|
||||
mConfig.lastModifiedDate = Instant.now().getEpochSecond();
|
||||
try (FileOutputStream fos = new FileOutputStream(mConfigFile)) {
|
||||
PersistentSystemFontConfig.writeToXml(fos, mConfig);
|
||||
} catch (Exception e) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE,
|
||||
"Failed to write config XML.", e);
|
||||
}
|
||||
mConfigVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a new font file, or updates an existing font file.
|
||||
*
|
||||
@@ -143,38 +213,92 @@ final class UpdatableFontDir {
|
||||
*
|
||||
* @param fd A file descriptor to the font file.
|
||||
* @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 IOException {
|
||||
void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) throws SystemFontException {
|
||||
synchronized (UpdatableFontDir.this) {
|
||||
File newDir = getRandomDir(mFilesDir);
|
||||
if (!newDir.mkdir()) {
|
||||
// TODO: Define and return an error code for API
|
||||
throw new IOException("Failed to create a new dir");
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_FAILED_TO_WRITE_FONT_FILE,
|
||||
"Failed to create font directory.");
|
||||
}
|
||||
boolean success = false;
|
||||
try {
|
||||
File tempNewFontFile = new File(newDir, "font.ttf");
|
||||
try (FileOutputStream out = new FileOutputStream(tempNewFontFile)) {
|
||||
FileUtils.copy(fd, out.getFD());
|
||||
} catch (IOException e) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_FAILED_TO_WRITE_FONT_FILE,
|
||||
"Failed to write font file to storage.", e);
|
||||
}
|
||||
try {
|
||||
// Do not parse font file before setting up fs-verity.
|
||||
// setUpFsverity throws IOException if failed.
|
||||
mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(),
|
||||
pkcs7Signature);
|
||||
} catch (IOException e) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_VERIFICATION_FAILURE,
|
||||
"Failed to setup fs-verity.", e);
|
||||
}
|
||||
String postScriptName;
|
||||
try {
|
||||
postScriptName = mParser.getPostScriptName(tempNewFontFile);
|
||||
} catch (IOException e) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_INVALID_FONT_FILE,
|
||||
"Failed to read PostScript name from font file", e);
|
||||
}
|
||||
if (postScriptName == null) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_MISSING_POST_SCRIPT_NAME,
|
||||
"Failed to read PostScript name from font file");
|
||||
}
|
||||
// Do not parse font file before setting up fs-verity.
|
||||
// setUpFsverity throws IOException if failed.
|
||||
mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(), pkcs7Signature);
|
||||
String postScriptName = mParser.getPostScriptName(tempNewFontFile);
|
||||
File newFontFile = new File(newDir, postScriptName + ALLOWED_EXTENSION);
|
||||
if (!mFsverityUtil.rename(tempNewFontFile, newFontFile)) {
|
||||
// TODO: Define and return an error code for API
|
||||
throw new IOException("Failed to rename");
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_FAILED_TO_WRITE_FONT_FILE,
|
||||
"Failed to move verified font file.");
|
||||
}
|
||||
FontFileInfo fontFileInfo = validateFontFile(newFontFile);
|
||||
if (fontFileInfo == null) {
|
||||
// TODO: Define and return an error code for API
|
||||
throw new IllegalArgumentException("Invalid file");
|
||||
|
||||
// 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.ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE,
|
||||
"Failed to write config XML.", e);
|
||||
}
|
||||
if (!addFileToMapLocked(fontFileInfo, false)) {
|
||||
// TODO: Define and return an error code for API
|
||||
throw new IllegalArgumentException("Version downgrade");
|
||||
|
||||
// Backup the mapping for rollback.
|
||||
HashMap<String, FontFileInfo> backup = new HashMap<>(mFontFileInfoMap);
|
||||
if (!addFileToMapIfNewerLocked(fontFileInfo, false)) {
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_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.ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE,
|
||||
"Failed to stage the config file.");
|
||||
}
|
||||
|
||||
|
||||
// Now font update is succeeded. Update config version.
|
||||
copied.copyTo(mConfig);
|
||||
mConfigVersion++;
|
||||
|
||||
success = true;
|
||||
} finally {
|
||||
if (!success) {
|
||||
@@ -207,7 +331,7 @@ final class UpdatableFontDir {
|
||||
* higher than the currently used font file (either in {@link #mFontFileInfoMap} or {@link
|
||||
* #mPreinstalledFontDirs}).
|
||||
*/
|
||||
private boolean addFileToMapLocked(FontFileInfo fontFileInfo, boolean deleteOldFile) {
|
||||
private boolean addFileToMapIfNewerLocked(FontFileInfo fontFileInfo, boolean deleteOldFile) {
|
||||
String name = fontFileInfo.getFile().getName();
|
||||
FontFileInfo existingInfo = mFontFileInfoMap.get(name);
|
||||
final boolean shouldAddToMap;
|
||||
@@ -224,13 +348,12 @@ final class UpdatableFontDir {
|
||||
FileUtils.deleteContentsAndDir(existingInfo.getRandomizedFontDir());
|
||||
}
|
||||
mFontFileInfoMap.put(name, fontFileInfo);
|
||||
return true;
|
||||
} else {
|
||||
if (deleteOldFile) {
|
||||
FileUtils.deleteContentsAndDir(fontFileInfo.getRandomizedFontDir());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return shouldAddToMap;
|
||||
}
|
||||
|
||||
private long getPreinstalledFontRevision(String name) {
|
||||
@@ -255,20 +378,23 @@ final class UpdatableFontDir {
|
||||
* returns a {@link FontFileInfo} on success. This method does not check if the font revision
|
||||
* is higher than the currently used font.
|
||||
*/
|
||||
@Nullable
|
||||
private FontFileInfo validateFontFile(File file) {
|
||||
@NonNull
|
||||
private FontFileInfo validateFontFile(File file) throws SystemFontException {
|
||||
if (!mFsverityUtil.hasFsverity(file.getAbsolutePath())) {
|
||||
Slog.w(TAG, "Font validation failed. Fs-verity is not enabled: " + file);
|
||||
return null;
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_VERIFICATION_FAILURE,
|
||||
"Font validation failed. Fs-verity is not enabled: " + file);
|
||||
}
|
||||
if (!validateFontFileName(file)) {
|
||||
Slog.w(TAG, "Font validation failed. Could not validate font file name: " + file);
|
||||
return null;
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_FONT_NAME_MISMATCH,
|
||||
"Font validation failed. Could not validate font file name: " + file);
|
||||
}
|
||||
long revision = getFontRevision(file);
|
||||
if (revision == -1) {
|
||||
Slog.w(TAG, "Font validation failed. Could not read font revision: " + file);
|
||||
return null;
|
||||
throw new SystemFontException(
|
||||
FontManager.ERROR_CODE_INVALID_FONT_FILE,
|
||||
"Font validation failed. Could not read font revision: " + file);
|
||||
}
|
||||
return new FontFileInfo(file, revision);
|
||||
}
|
||||
@@ -318,4 +444,14 @@ final class UpdatableFontDir {
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/* package */ FontConfig getSystemFontConfig() {
|
||||
synchronized (UpdatableFontDir.this) {
|
||||
return SystemFonts.getSystemFontConfig(
|
||||
getFontFileMap(),
|
||||
mConfig.lastModifiedDate,
|
||||
mConfigVersion
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 com.android.server.graphics.fonts;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Presubmit
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class PersistentSystemFontConfigTest {
|
||||
|
||||
@Test
|
||||
public void testWriteRead() throws IOException, XmlPullParserException {
|
||||
long expectedModifiedDate = 1234567890;
|
||||
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
|
||||
config.lastModifiedDate = expectedModifiedDate;
|
||||
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
PersistentSystemFontConfig.writeToXml(baos, config);
|
||||
|
||||
byte[] written = baos.toByteArray();
|
||||
assertThat(written).isNotEmpty();
|
||||
|
||||
try (ByteArrayInputStream bais = new ByteArrayInputStream(written)) {
|
||||
PersistentSystemFontConfig.Config another = new PersistentSystemFontConfig.Config();
|
||||
PersistentSystemFontConfig.loadFromXml(bais, another);
|
||||
|
||||
assertThat(another.lastModifiedDate).isEqualTo(expectedModifiedDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrongType() throws IOException, XmlPullParserException {
|
||||
String xml = "<fontConfig>"
|
||||
+ " <lastModifiedDate value=\"string\" />"
|
||||
+ "</fontConfig>";
|
||||
|
||||
try (ByteArrayInputStream bais =
|
||||
new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) {
|
||||
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
|
||||
PersistentSystemFontConfig.loadFromXml(bais, config);
|
||||
assertThat(config.lastModifiedDate).isEqualTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.fonts.FontManager;
|
||||
import android.os.FileUtils;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
@@ -36,6 +37,7 @@ 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;
|
||||
@@ -107,6 +109,7 @@ public final class UpdatableFontDirTest {
|
||||
|
||||
private File mCacheDir;
|
||||
private File mUpdatableFontFilesDir;
|
||||
private File mConfigFile;
|
||||
private List<File> mPreinstalledFontDirs;
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
@@ -124,6 +127,7 @@ public final class UpdatableFontDirTest {
|
||||
for (File dir : mPreinstalledFontDirs) {
|
||||
dir.mkdir();
|
||||
}
|
||||
mConfigFile = new File(mCacheDir, "config.xml");
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -133,19 +137,30 @@ public final class UpdatableFontDirTest {
|
||||
|
||||
@Test
|
||||
public void construct() throws Exception {
|
||||
long expectedModifiedDate = 1234567890;
|
||||
FakeFontFileParser parser = new FakeFontFileParser();
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
|
||||
config.lastModifiedDate = expectedModifiedDate;
|
||||
writeConfig(config, mConfigFile);
|
||||
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedDate())
|
||||
.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);
|
||||
// Four font dirs are created.
|
||||
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
|
||||
//
|
||||
assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedDate())
|
||||
.isNotEqualTo(expectedModifiedDate);
|
||||
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
|
||||
assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(3);
|
||||
assertThat(dir.getFontFileMap()).containsKey("bar.ttf");
|
||||
@@ -159,7 +174,8 @@ public final class UpdatableFontDirTest {
|
||||
FakeFontFileParser parser = new FakeFontFileParser();
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
assertThat(dir.getFontFileMap()).isEmpty();
|
||||
}
|
||||
|
||||
@@ -168,7 +184,8 @@ public final class UpdatableFontDirTest {
|
||||
FakeFontFileParser parser = new FakeFontFileParser();
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
|
||||
installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
|
||||
installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
|
||||
@@ -179,7 +196,8 @@ public final class UpdatableFontDirTest {
|
||||
fakeFsverityUtil.remove(
|
||||
dirForPreparation.getFontFileMap().get("foo.ttf").getAbsolutePath());
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
assertThat(dir.getFontFileMap()).isEmpty();
|
||||
// All font dirs (including dir for "bar.ttf") should be deleted.
|
||||
assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
|
||||
@@ -190,7 +208,8 @@ public final class UpdatableFontDirTest {
|
||||
FakeFontFileParser parser = new FakeFontFileParser();
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
|
||||
installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
|
||||
installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
|
||||
@@ -202,7 +221,8 @@ public final class UpdatableFontDirTest {
|
||||
FileUtils.stringToFile(dirForPreparation.getFontFileMap().get("foo.ttf"), "bar,4");
|
||||
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
assertThat(dir.getFontFileMap()).isEmpty();
|
||||
// All font dirs (including dir for "bar.ttf") should be deleted.
|
||||
assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
|
||||
@@ -213,7 +233,8 @@ public final class UpdatableFontDirTest {
|
||||
FakeFontFileParser parser = new FakeFontFileParser();
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
|
||||
installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
|
||||
installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
|
||||
@@ -226,7 +247,8 @@ public final class UpdatableFontDirTest {
|
||||
FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "bar.ttf"), "bar,1");
|
||||
FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(1), "bar.ttf"), "bar,2");
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
// For foo.ttf, preinstalled font (revision 5) should be used.
|
||||
assertThat(dir.getFontFileMap()).doesNotContainKey("foo.ttf");
|
||||
// For bar.ttf, updated font (revision 4) should be used.
|
||||
@@ -238,12 +260,23 @@ public final class UpdatableFontDirTest {
|
||||
assertThat(mUpdatableFontFilesDir.list()).hasLength(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void construct_failedToLoadConfig() throws Exception {
|
||||
FakeFontFileParser parser = new FakeFontFileParser();
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
new File("/dev/null"));
|
||||
assertThat(dir.getFontFileMap()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void installFontFile() throws Exception {
|
||||
FakeFontFileParser parser = new FakeFontFileParser();
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
|
||||
installFontFile(dir, "test,1", GOOD_SIGNATURE);
|
||||
assertThat(dir.getFontFileMap()).containsKey("test.ttf");
|
||||
@@ -255,7 +288,8 @@ public final class UpdatableFontDirTest {
|
||||
FakeFontFileParser parser = new FakeFontFileParser();
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
|
||||
installFontFile(dir, "test,1", GOOD_SIGNATURE);
|
||||
Map<String, File> mapBeforeUpgrade = dir.getFontFileMap();
|
||||
@@ -272,14 +306,15 @@ public final class UpdatableFontDirTest {
|
||||
FakeFontFileParser parser = new FakeFontFileParser();
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
|
||||
installFontFile(dir, "test,2", GOOD_SIGNATURE);
|
||||
try {
|
||||
installFontFile(dir, "test,1", GOOD_SIGNATURE);
|
||||
fail("Expect IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expect
|
||||
} catch (FontManagerService.SystemFontException e) {
|
||||
assertThat(e.getErrorCode()).isEqualTo(FontManager.ERROR_CODE_DOWNGRADING);
|
||||
}
|
||||
assertThat(dir.getFontFileMap()).containsKey("test.ttf");
|
||||
assertWithMessage("Font should not be downgraded to an older revision")
|
||||
@@ -291,7 +326,8 @@ public final class UpdatableFontDirTest {
|
||||
FakeFontFileParser parser = new FakeFontFileParser();
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
|
||||
installFontFile(dir, "foo,1", GOOD_SIGNATURE);
|
||||
installFontFile(dir, "bar,2", GOOD_SIGNATURE);
|
||||
@@ -302,17 +338,19 @@ public final class UpdatableFontDirTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void installFontFile_invalidSignature() {
|
||||
public void installFontFile_invalidSignature() throws Exception {
|
||||
FakeFontFileParser parser = new FakeFontFileParser();
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
|
||||
try {
|
||||
installFontFile(dir, "test,1", "Invalid signature");
|
||||
fail("Expect IOException");
|
||||
} catch (IOException e) {
|
||||
// Expect
|
||||
fail("Expect SystemFontException");
|
||||
} catch (FontManagerService.SystemFontException e) {
|
||||
assertThat(e.getErrorCode())
|
||||
.isEqualTo(FontManager.ERROR_CODE_VERIFICATION_FAILURE);
|
||||
}
|
||||
assertThat(dir.getFontFileMap()).isEmpty();
|
||||
}
|
||||
@@ -323,23 +361,155 @@ public final class UpdatableFontDirTest {
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test,1");
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
|
||||
try {
|
||||
installFontFile(dir, "test,1", GOOD_SIGNATURE);
|
||||
fail("Expect IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expect
|
||||
} catch (FontManagerService.SystemFontException e) {
|
||||
assertThat(e.getErrorCode()).isEqualTo(FontManager.ERROR_CODE_DOWNGRADING);
|
||||
}
|
||||
assertThat(dir.getFontFileMap()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void installFontFile_failedToWriteConfigXml() throws Exception {
|
||||
long expectedModifiedDate = 1234567890;
|
||||
FakeFontFileParser parser = new FakeFontFileParser();
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test,1");
|
||||
|
||||
File readonlyDir = new File(mCacheDir, "readonly");
|
||||
assertThat(readonlyDir.mkdir()).isTrue();
|
||||
File readonlyFile = new File(readonlyDir, "readonly_config.xml");
|
||||
|
||||
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
|
||||
config.lastModifiedDate = expectedModifiedDate;
|
||||
writeConfig(config, readonlyFile);
|
||||
|
||||
assertThat(readonlyDir.setWritable(false, false)).isTrue();
|
||||
try {
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
readonlyFile);
|
||||
|
||||
try {
|
||||
installFontFile(dir, "test,2", GOOD_SIGNATURE);
|
||||
} catch (FontManagerService.SystemFontException e) {
|
||||
assertThat(e.getErrorCode())
|
||||
.isEqualTo(FontManager.ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE);
|
||||
}
|
||||
assertThat(dir.getSystemFontConfig().getLastModifiedDate())
|
||||
.isEqualTo(expectedModifiedDate);
|
||||
assertThat(dir.getFontFileMap()).isEmpty();
|
||||
} finally {
|
||||
assertThat(readonlyDir.setWritable(true, true)).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void installFontFile_failedToParsePostScript() throws Exception {
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs,
|
||||
new UpdatableFontDir.FontFileParser() {
|
||||
@Override
|
||||
public String getPostScriptName(File file) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRevision(File file) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
}, fakeFsverityUtil, mConfigFile);
|
||||
|
||||
try {
|
||||
installFontFile(dir, "foo,1", GOOD_SIGNATURE);
|
||||
fail("Expect SystemFontException");
|
||||
} catch (FontManagerService.SystemFontException e) {
|
||||
assertThat(e.getErrorCode())
|
||||
.isEqualTo(FontManager.ERROR_CODE_MISSING_POST_SCRIPT_NAME);
|
||||
}
|
||||
assertThat(dir.getFontFileMap()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void installFontFile_failedToParsePostScriptName_invalidFont() throws Exception {
|
||||
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs,
|
||||
new UpdatableFontDir.FontFileParser() {
|
||||
@Override
|
||||
public String getPostScriptName(File file) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRevision(File file) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
}, fakeFsverityUtil, mConfigFile);
|
||||
|
||||
try {
|
||||
installFontFile(dir, "foo,1", GOOD_SIGNATURE);
|
||||
fail("Expect SystemFontException");
|
||||
} catch (FontManagerService.SystemFontException e) {
|
||||
assertThat(e.getErrorCode())
|
||||
.isEqualTo(FontManager.ERROR_CODE_INVALID_FONT_FILE);
|
||||
}
|
||||
assertThat(dir.getFontFileMap()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void installFontFile_renameToPsNameFailure() throws Exception {
|
||||
UpdatableFontDir.FsverityUtil fakeFsverityUtil = new UpdatableFontDir.FsverityUtil() {
|
||||
private final FakeFsverityUtil mFake = new FakeFsverityUtil();
|
||||
|
||||
@Override
|
||||
public boolean hasFsverity(String path) {
|
||||
return mFake.hasFsverity(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException {
|
||||
mFake.setUpFsverity(path, pkcs7Signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rename(File src, File dest) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
FakeFontFileParser parser = new FakeFontFileParser();
|
||||
UpdatableFontDir dir = new UpdatableFontDir(
|
||||
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
|
||||
mConfigFile);
|
||||
|
||||
try {
|
||||
installFontFile(dir, "foo,1", GOOD_SIGNATURE);
|
||||
fail("Expect SystemFontException");
|
||||
} catch (FontManagerService.SystemFontException e) {
|
||||
assertThat(e.getErrorCode())
|
||||
.isEqualTo(FontManager.ERROR_CODE_FAILED_TO_WRITE_FONT_FILE);
|
||||
}
|
||||
assertThat(dir.getFontFileMap()).isEmpty();
|
||||
}
|
||||
|
||||
private void installFontFile(UpdatableFontDir dir, String content, String signature)
|
||||
throws IOException {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeConfig(PersistentSystemFontConfig.Config config,
|
||||
File file) throws IOException {
|
||||
try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||
PersistentSystemFontConfig.writeToXml(fos, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user