summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2021-01-28 01:49:59 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2021-01-28 01:49:59 +0000
commit9b9486ebf298438c3144bbc9dd27102780f73b52 (patch)
tree375bd4932948f2eae35ab33f010bcaef282cc315
parent299897e9baf78b9c0dc1d1007fea74efcbaa6137 (diff)
parent9387e7f4cda0f24eb88231254c1929b400df8935 (diff)
Merge "Add more shell command for font" into sc-dev
-rw-r--r--core/java/android/graphics/fonts/FontManager.java113
-rw-r--r--core/java/android/text/FontConfig.java33
-rw-r--r--core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java5
-rw-r--r--core/tests/coretests/src/android/text/FontFallbackSetup.java2
-rw-r--r--graphics/java/android/graphics/FontListParser.java16
-rw-r--r--graphics/java/android/graphics/fonts/SystemFonts.java19
-rw-r--r--services/core/java/com/android/server/graphics/fonts/FontManagerService.java78
-rw-r--r--services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java206
-rw-r--r--services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java114
-rw-r--r--services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java198
-rw-r--r--services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java75
-rw-r--r--services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java218
12 files changed, 949 insertions, 128 deletions
diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java
index ea6cf2f44be9..eca56b375dd4 100644
--- a/core/java/android/graphics/fonts/FontManager.java
+++ b/core/java/android/graphics/fonts/FontManager.java
@@ -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;
}
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 82d7399c86e0..53fe1ba9c4b4 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -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;
}
/**
@@ -88,6 +93,26 @@ public final class FontConfig implements Parcelable {
}
/**
+ * 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.
* @hide
@@ -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
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 65ea2a8373aa..3df2e90241ff 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -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);
}
diff --git a/core/tests/coretests/src/android/text/FontFallbackSetup.java b/core/tests/coretests/src/android/text/FontFallbackSetup.java
index e301037d01f1..90a6ca3cd1d1 100644
--- a/core/tests/coretests/src/android/text/FontFallbackSetup.java
+++ b/core/tests/coretests/src/android/text/FontFallbackSetup.java
@@ -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);
}
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index c8ff95d15626..bb795cd99e6e 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -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);
}
/**
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index a41215f9cae1..c166e12fc6bf 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -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);
}
}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index bda4240a5858..5b3db011b427 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -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;
}
}
+
}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index acb582637e55..fd5c020b1a15 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -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();
- if (cmd == null) {
- return shell.handleDefaultCommands(null);
+ 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;
+ }
- final FontManagerService.SystemFontSettings settings = mService.getCurrentFontSettings();
+ 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");
+ }
- switch (cmd) {
- case "dump":
- if (!DumpUtils.checkDumpPermission(ctx, TAG, shell.getErrPrintWriter())) {
- return 1;
+ 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");
}
- 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);
- }
+ byte[] signature = new byte[len];
+ if (sigFis.read(signature, 0, len) != len) {
+ throw new SystemFontException(
+ FontManager.ERROR_CODE_INVALID_SIGNATURE_FILE,
+ "Invalid read length");
}
- return 0;
- default:
- shell.handleDefaultCommands(cmd);
+ 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);
+ }
+
+ 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;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
new file mode 100644
index 000000000000..f0d14ba80383
--- /dev/null
+++ b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
@@ -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;
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 8da579fd4fa3..8ec72770d782 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -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
+ );
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
new file mode 100644
index 000000000000..c10cee9b4c3d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
@@ -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);
+ }
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 75bf1e6bd485..0e872d96bd85 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -22,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.
@@ -239,11 +261,22 @@ public final class UpdatableFontDirTest {
}
@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);
+ }
+ }
}