diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java index f0b218ce5343f..abb4f9fa7eefa 100644 --- a/core/java/android/graphics/fonts/FontManager.java +++ b/core/java/android/graphics/fonts/FontManager.java @@ -261,7 +261,7 @@ public class FontManager { @IntRange(from = 0) int baseVersion ) { try { - return mIFontManager.updateFont(pfd, signature, baseVersion); + return mIFontManager.updateFont(baseVersion, new FontUpdateRequest(pfd, signature)); } catch (RemoteException e) { Log.e(TAG, "Failed to call updateFont API", e); return RESULT_ERROR_REMOTE_EXCEPTION; diff --git a/core/java/android/graphics/fonts/SystemFontState.aidl b/core/java/android/graphics/fonts/FontUpdateRequest.aidl similarity index 95% rename from core/java/android/graphics/fonts/SystemFontState.aidl rename to core/java/android/graphics/fonts/FontUpdateRequest.aidl index 19b20f268486d..f6cb373b7ed2a 100644 --- a/core/java/android/graphics/fonts/SystemFontState.aidl +++ b/core/java/android/graphics/fonts/FontUpdateRequest.aidl @@ -17,4 +17,4 @@ package android.graphics.fonts; /** @hide */ -parcelable SystemFontState; \ No newline at end of file +parcelable FontUpdateRequest; \ No newline at end of file diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java new file mode 100644 index 0000000000000..db047f8ba6961 --- /dev/null +++ b/core/java/android/graphics/fonts/FontUpdateRequest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics.fonts; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; + +/** + * Represents a font update request. Currently only font install request is supported. + * @hide + */ +// TODO: Support font config update. +public final class FontUpdateRequest implements Parcelable { + + public static final Creator CREATOR = new Creator() { + @Override + public FontUpdateRequest createFromParcel(Parcel in) { + return new FontUpdateRequest(in); + } + + @Override + public FontUpdateRequest[] newArray(int size) { + return new FontUpdateRequest[size]; + } + }; + + @NonNull + private final ParcelFileDescriptor mFd; + @NonNull + private final byte[] mSignature; + + public FontUpdateRequest(@NonNull ParcelFileDescriptor fd, @NonNull byte[] signature) { + mFd = fd; + mSignature = signature; + } + + private FontUpdateRequest(Parcel in) { + mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader()); + mSignature = in.readBlob(); + } + + @NonNull + public ParcelFileDescriptor getFd() { + return mFd; + } + + @NonNull + public byte[] getSignature() { + return mSignature; + } + + @Override + public int describeContents() { + return Parcelable.CONTENTS_FILE_DESCRIPTOR; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mFd, flags); + dest.writeBlob(mSignature); + } +} diff --git a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl index cafe0de58b78e..dfcc91421b9e7 100644 --- a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl +++ b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl @@ -17,8 +17,8 @@ package com.android.internal.graphics.fonts; import android.os.ParcelFileDescriptor; +import android.graphics.fonts.FontUpdateRequest; import android.text.FontConfig; -import android.graphics.fonts.SystemFontState; /** * System private interface for talking with @@ -28,5 +28,5 @@ import android.graphics.fonts.SystemFontState; interface IFontManager { FontConfig getFontConfig(); - int updateFont(in ParcelFileDescriptor fd, in byte[] signature, int baseVersion); + int updateFont(int baseVersion, in FontUpdateRequest request); } 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 90368129e78eb..691438f3f4fb4 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -24,8 +24,8 @@ import android.graphics.Typeface; import android.graphics.fonts.FontFamily; import android.graphics.fonts.FontFileUtil; import android.graphics.fonts.FontManager; +import android.graphics.fonts.FontUpdateRequest; import android.graphics.fonts.SystemFonts; -import android.os.ParcelFileDescriptor; import android.os.ResultReceiver; import android.os.SharedMemory; import android.os.ShellCallback; @@ -54,6 +54,7 @@ import java.nio.NioUtils; import java.nio.channels.FileChannel; import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -71,14 +72,15 @@ public final class FontManagerService extends IFontManager.Stub { } @Override - public int updateFont(ParcelFileDescriptor fd, byte[] signature, int baseVersion) { - Objects.requireNonNull(fd); - Objects.requireNonNull(signature); + public int updateFont(int baseVersion, @NonNull FontUpdateRequest request) { + Objects.requireNonNull(request); + Objects.requireNonNull(request.getFd()); + Objects.requireNonNull(request.getSignature()); Preconditions.checkArgumentNonnegative(baseVersion); getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS, "UPDATE_FONTS permission required."); try { - installFontFile(fd.getFileDescriptor(), signature, baseVersion); + update(baseVersion, Collections.singletonList(request)); return FontManager.RESULT_SUCCESS; } catch (SystemFontException e) { Slog.e(TAG, "Failed to update font file", e); @@ -249,7 +251,7 @@ public final class FontManagerService extends IFontManager.Stub { } } - /* package */ void installFontFile(FileDescriptor fd, byte[] pkcs7Signature, int baseVersion) + /* package */ void update(int baseVersion, List requests) throws SystemFontException { if (mUpdatableFontDir == null) { throw new SystemFontException( @@ -265,7 +267,7 @@ public final class FontManagerService extends IFontManager.Stub { "The base config version is older than current."); } try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) { - mUpdatableFontDir.installFontFile(fd, pkcs7Signature); + mUpdatableFontDir.update(requests); updateSerializedFontMap(); } } 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 2029f395a352b..cf9a79f69f19f 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java @@ -24,6 +24,7 @@ import android.content.Context; import android.graphics.fonts.Font; import android.graphics.fonts.FontFamily; import android.graphics.fonts.FontManager; +import android.graphics.fonts.FontUpdateRequest; import android.graphics.fonts.FontVariationAxis; import android.graphics.fonts.SystemFonts; import android.os.Binder; @@ -44,6 +45,7 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -315,6 +317,7 @@ public class FontManagerShellCommand extends ShellCommand { "Signature file argument is required."); } + // TODO: close fontFd and sigFd. ParcelFileDescriptor fontFd = shell.openFileForSystem(fontPath, "r"); if (fontFd == null) { throw new SystemFontException( @@ -330,29 +333,24 @@ public class FontManagerShellCommand extends ShellCommand { } try (FileInputStream sigFis = new FileInputStream(sigFd.getFileDescriptor())) { - try (FileInputStream fontFis = new FileInputStream(fontFd.getFileDescriptor())) { - int len = sigFis.available(); - if (len > MAX_SIGNATURE_FILE_SIZE_BYTES) { - throw new SystemFontException( - FontManager.RESULT_ERROR_SIGNATURE_TOO_LARGE, - "Signature file is too large"); - } - byte[] signature = new byte[len]; - if (sigFis.read(signature, 0, len) != len) { - throw new SystemFontException( - FontManager.RESULT_ERROR_INVALID_SIGNATURE_FILE, - "Invalid read length"); - } - mService.installFontFile(fontFis.getFD(), signature, -1); - } catch (IOException e) { + int len = sigFis.available(); + if (len > MAX_SIGNATURE_FILE_SIZE_BYTES) { + throw new SystemFontException( + FontManager.RESULT_ERROR_SIGNATURE_TOO_LARGE, + "Signature file is too large"); + } + byte[] signature = new byte[len]; + if (sigFis.read(signature, 0, len) != len) { throw new SystemFontException( FontManager.RESULT_ERROR_INVALID_SIGNATURE_FILE, - "Failed to read signature file.", e); + "Invalid read length"); } + mService.update( + -1, Collections.singletonList(new FontUpdateRequest(fontFd, signature))); } catch (IOException e) { throw new SystemFontException( - FontManager.RESULT_ERROR_INVALID_FONT_FILE, - "Failed to read font files.", e); + FontManager.RESULT_ERROR_INVALID_SIGNATURE_FILE, + "Failed to read signature file.", e); } shell.getOutPrintWriter().println("Success"); // TODO: Output more details. diff --git a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java index f0d14ba80383c..017f11ceaf060 100644 --- a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java +++ b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java @@ -18,6 +18,7 @@ package com.android.server.graphics.fonts; import android.annotation.NonNull; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Slog; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; @@ -29,24 +30,19 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Set; /* package */ class PersistentSystemFontConfig { private static final String TAG = "PersistentSystemFontConfig"; private static final String TAG_ROOT = "fontConfig"; private static final String TAG_LAST_MODIFIED_DATE = "lastModifiedDate"; - private static final String TAG_VALUE = "value"; + private static final String TAG_UPDATED_FONT_DIR = "updatedFontDir"; + private static final String ATTR_VALUE = "value"; /* package */ static class Config { public long lastModifiedDate; - - public void reset() { - lastModifiedDate = 0; - } - - public void copyTo(@NonNull Config out) { - out.lastModifiedDate = lastModifiedDate; - } + public final Set updatedFontDirs = new ArraySet<>(); } /** @@ -54,7 +50,6 @@ import java.io.OutputStream; */ public static void loadFromXml(@NonNull InputStream is, @NonNull Config out) throws XmlPullParserException, IOException { - out.reset(); TypedXmlPullParser parser = Xml.resolvePullParser(is); int type; @@ -72,7 +67,10 @@ import java.io.OutputStream; } else if (depth == 2) { switch (tag) { case TAG_LAST_MODIFIED_DATE: - out.lastModifiedDate = parseLongAttribute(parser, TAG_VALUE, 0); + out.lastModifiedDate = parseLongAttribute(parser, ATTR_VALUE, 0); + break; + case TAG_UPDATED_FONT_DIR: + out.updatedFontDirs.add(getAttribute(parser, ATTR_VALUE)); break; default: Slog.w(TAG, "Skipping unknown tag: " + tag); @@ -92,8 +90,13 @@ import java.io.OutputStream; out.startTag(null, TAG_ROOT); out.startTag(null, TAG_LAST_MODIFIED_DATE); - out.attribute(null, TAG_VALUE, Long.toString(config.lastModifiedDate)); + out.attribute(null, ATTR_VALUE, Long.toString(config.lastModifiedDate)); out.endTag(null, TAG_LAST_MODIFIED_DATE); + for (String dir : config.updatedFontDirs) { + out.startTag(null, TAG_UPDATED_FONT_DIR); + out.attribute(null, ATTR_VALUE, dir); + out.endTag(null, TAG_UPDATED_FONT_DIR); + } out.endTag(null, TAG_ROOT); out.endDocument(); @@ -111,4 +114,9 @@ import java.io.OutputStream; } } + @NonNull + private static String getAttribute(TypedXmlPullParser parser, String attr) { + final String value = parser.getAttributeValue(null /* namespace */, attr); + return value == null ? "" : value; + } } 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 8fac0865dd155..afc97c7fd8038 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -21,6 +21,7 @@ import static com.android.server.graphics.fonts.FontManagerService.SystemFontExc import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.fonts.FontManager; +import android.graphics.fonts.FontUpdateRequest; import android.graphics.fonts.SystemFonts; import android.os.FileUtils; import android.system.ErrnoException; @@ -72,15 +73,6 @@ final class UpdatableFontDir { boolean rename(File src, File dest); } - /** Interface to mock persistent configuration */ - interface PersistentConfig { - void loadFromXml(PersistentSystemFontConfig.Config out) - throws XmlPullParserException, IOException; - void writeToXml(PersistentSystemFontConfig.Config config) - throws IOException; - boolean rename(File src, File dest); - } - /** Data class to hold font file path and revision. */ private static final class FontFileInfo { private final File mFile; @@ -116,9 +108,7 @@ final class UpdatableFontDir { private final File mConfigFile; private final File mTmpConfigFile; - private final PersistentSystemFontConfig.Config mConfig = - new PersistentSystemFontConfig.Config(); - + private long mLastModifiedDate; private int mConfigVersion = 1; /** @@ -145,22 +135,36 @@ final class UpdatableFontDir { } /* package */ void loadFontFileMap() { - boolean success = false; - - try (FileInputStream fis = new FileInputStream(mConfigFile)) { - PersistentSystemFontConfig.loadFromXml(fis, mConfig); - } catch (IOException | XmlPullParserException e) { - mConfig.reset(); - } - mFontFileInfoMap.clear(); + mLastModifiedDate = 0; + boolean success = false; try { + PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); + try (FileInputStream fis = new FileInputStream(mConfigFile)) { + PersistentSystemFontConfig.loadFromXml(fis, config); + } catch (IOException | XmlPullParserException e) { + Slog.e(TAG, "Failed to load config xml file", e); + return; + } + mLastModifiedDate = config.lastModifiedDate; + File[] dirs = mFilesDir.listFiles(); if (dirs == null) return; for (File dir : dirs) { - if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) return; + if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) { + Slog.e(TAG, "Unexpected dir found: " + dir); + return; + } + if (!config.updatedFontDirs.contains(dir.getName())) { + Slog.i(TAG, "Deleting obsolete dir: " + dir); + FileUtils.deleteContentsAndDir(dir); + continue; + } File[] files = dir.listFiles(); - if (files == null || files.length != 1) return; + if (files == null || files.length != 1) { + Slog.e(TAG, "Unexpected files in dir: " + dir); + return; + } FontFileInfo fontFileInfo = validateFontFile(files[0]); addFileToMapIfNewer(fontFileInfo, true /* deleteOldFile */); } @@ -173,6 +177,7 @@ final class UpdatableFontDir { // Delete all files just in case if we find a problematic file. if (!success) { mFontFileInfoMap.clear(); + mLastModifiedDate = 0; FileUtils.deleteContents(mFilesDir); } } @@ -182,10 +187,9 @@ final class UpdatableFontDir { mFontFileInfoMap.clear(); FileUtils.deleteContents(mFilesDir); - mConfig.reset(); - mConfig.lastModifiedDate = Instant.now().getEpochSecond(); + mLastModifiedDate = Instant.now().getEpochSecond(); try (FileOutputStream fos = new FileOutputStream(mConfigFile)) { - PersistentSystemFontConfig.writeToXml(fos, mConfig); + PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig()); } catch (Exception e) { throw new SystemFontException( FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG, @@ -194,6 +198,47 @@ final class UpdatableFontDir { mConfigVersion++; } + /** + * Applies multiple {@link FontUpdateRequest}s in transaction. + * If one of the request fails, the fonts and config are rolled back to the previous state + * before this method is called. + */ + public void update(List requests) throws SystemFontException { + // Backup the mapping for rollback. + HashMap backupMap = new HashMap<>(mFontFileInfoMap); + long backupLastModifiedDate = mLastModifiedDate; + boolean success = false; + try { + for (FontUpdateRequest request : requests) { + installFontFile(request.getFd().getFileDescriptor(), request.getSignature()); + } + + // Write config file. + mLastModifiedDate = Instant.now().getEpochSecond(); + try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) { + PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig()); + } catch (Exception e) { + throw new SystemFontException( + FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG, + "Failed to write config XML.", e); + } + + if (!mFsverityUtil.rename(mTmpConfigFile, mConfigFile)) { + throw new SystemFontException( + FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG, + "Failed to stage the config file."); + } + mConfigVersion++; + success = true; + } finally { + if (!success) { + mFontFileInfoMap.clear(); + mFontFileInfoMap.putAll(backupMap); + mLastModifiedDate = backupLastModifiedDate; + } + } + } + /** * Installs a new font file, or updates an existing font file. * @@ -205,7 +250,8 @@ final class UpdatableFontDir { * @param pkcs7Signature A PKCS#7 detached signature to enable fs-verity for the font file. * @throws SystemFontException if error occurs. */ - void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) throws SystemFontException { + private void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) + throws SystemFontException { File newDir = getRandomDir(mFilesDir); if (!newDir.mkdir()) { throw new SystemFontException( @@ -268,42 +314,11 @@ final class UpdatableFontDir { "Failed to change mode to 711", e); } FontFileInfo fontFileInfo = validateFontFile(newFontFile); - - // Write config file. - PersistentSystemFontConfig.Config copied = new PersistentSystemFontConfig.Config(); - mConfig.copyTo(copied); - - copied.lastModifiedDate = Instant.now().getEpochSecond(); - try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) { - PersistentSystemFontConfig.writeToXml(fos, copied); - } catch (Exception e) { - throw new SystemFontException( - FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG, - "Failed to write config XML.", e); - } - - // Backup the mapping for rollback. - HashMap backup = new HashMap<>(mFontFileInfoMap); if (!addFileToMapIfNewer(fontFileInfo, false)) { throw new SystemFontException( FontManager.RESULT_ERROR_DOWNGRADING, "Downgrading font file is forbidden."); } - - if (!mFsverityUtil.rename(mTmpConfigFile, mConfigFile)) { - // If we fail to stage the config file, need to rollback the config. - mFontFileInfoMap.clear(); - mFontFileInfoMap.putAll(backup); - throw new SystemFontException( - FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG, - "Failed to stage the config file."); - } - - - // Now font update is succeeded. Update config version. - copied.copyTo(mConfig); - mConfigVersion++; - success = true; } finally { if (!success) { @@ -439,6 +454,15 @@ final class UpdatableFontDir { } } + private PersistentSystemFontConfig.Config getPersistentConfig() { + PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); + config.lastModifiedDate = mLastModifiedDate; + for (FontFileInfo info : mFontFileInfoMap.values()) { + config.updatedFontDirs.add(info.getRandomizedFontDir().getName()); + } + return config; + } + Map getFontFileMap() { Map map = new HashMap<>(); for (Map.Entry entry : mFontFileInfoMap.entrySet()) { @@ -448,11 +472,7 @@ final class UpdatableFontDir { } /* package */ FontConfig getSystemFontConfig() { - return SystemFonts.getSystemFontConfig( - getFontFileMap(), - mConfig.lastModifiedDate, - mConfigVersion - ); + return SystemFonts.getSystemFontConfig(getFontFileMap(), mLastModifiedDate, mConfigVersion); } /* package */ int getConfigVersion() { 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 index c10cee9b4c3d0..86054e48fe6d7 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java @@ -42,6 +42,8 @@ public final class PersistentSystemFontConfigTest { long expectedModifiedDate = 1234567890; PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); config.lastModifiedDate = expectedModifiedDate; + config.updatedFontDirs.add("~~abc"); + config.updatedFontDirs.add("~~def"); try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { PersistentSystemFontConfig.writeToXml(baos, config); @@ -54,6 +56,7 @@ public final class PersistentSystemFontConfigTest { PersistentSystemFontConfig.loadFromXml(bais, another); assertThat(another.lastModifiedDate).isEqualTo(expectedModifiedDate); + assertThat(another.updatedFontDirs).containsExactly("~~abc", "~~def"); } } } 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 833103142ccf4..cb83b0f7f2f76 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 @@ -23,7 +23,9 @@ import static org.junit.Assert.fail; import android.content.Context; import android.graphics.fonts.FontManager; +import android.graphics.fonts.FontUpdateRequest; import android.os.FileUtils; +import android.os.ParcelFileDescriptor; import android.platform.test.annotations.Presubmit; import android.system.Os; @@ -37,11 +39,12 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -150,13 +153,13 @@ public final class UpdatableFontDirTest { dirForPreparation.loadFontFileMap(); assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis()) .isEqualTo(expectedModifiedDate); - installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE); - installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE); - installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE); - installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE); + dirForPreparation.update(Arrays.asList( + newFontUpdateRequest("foo,1", GOOD_SIGNATURE), + newFontUpdateRequest("bar,2", GOOD_SIGNATURE), + newFontUpdateRequest("foo,3", GOOD_SIGNATURE), + newFontUpdateRequest("bar,4", GOOD_SIGNATURE))); // Four font dirs are created. assertThat(mUpdatableFontFilesDir.list()).hasLength(4); - // assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis()) .isNotEqualTo(expectedModifiedDate); @@ -191,10 +194,11 @@ public final class UpdatableFontDirTest { mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, mConfigFile); dirForPreparation.loadFontFileMap(); - installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE); - installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE); - installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE); - installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE); + dirForPreparation.update(Arrays.asList( + newFontUpdateRequest("foo,1", GOOD_SIGNATURE), + newFontUpdateRequest("bar,2", GOOD_SIGNATURE), + newFontUpdateRequest("foo,3", GOOD_SIGNATURE), + newFontUpdateRequest("bar,4", GOOD_SIGNATURE))); // Four font dirs are created. assertThat(mUpdatableFontFilesDir.list()).hasLength(4); @@ -217,10 +221,11 @@ public final class UpdatableFontDirTest { mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, mConfigFile); dirForPreparation.loadFontFileMap(); - installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE); - installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE); - installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE); - installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE); + dirForPreparation.update(Arrays.asList( + newFontUpdateRequest("foo,1", GOOD_SIGNATURE), + newFontUpdateRequest("bar,2", GOOD_SIGNATURE), + newFontUpdateRequest("foo,3", GOOD_SIGNATURE), + newFontUpdateRequest("bar,4", GOOD_SIGNATURE))); // Four font dirs are created. assertThat(mUpdatableFontFilesDir.list()).hasLength(4); @@ -244,10 +249,11 @@ public final class UpdatableFontDirTest { mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, mConfigFile); dirForPreparation.loadFontFileMap(); - installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE); - installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE); - installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE); - installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE); + dirForPreparation.update(Arrays.asList( + newFontUpdateRequest("foo,1", GOOD_SIGNATURE), + newFontUpdateRequest("bar,2", GOOD_SIGNATURE), + newFontUpdateRequest("foo,3", GOOD_SIGNATURE), + newFontUpdateRequest("bar,4", GOOD_SIGNATURE))); // Four font dirs are created. assertThat(mUpdatableFontFilesDir.list()).hasLength(4); @@ -281,6 +287,34 @@ public final class UpdatableFontDirTest { assertThat(dir.getFontFileMap()).isEmpty(); } + @Test + public void construct_afterBatchFailure() throws Exception { + FakeFontFileParser parser = new FakeFontFileParser(); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dirForPreparation = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); + dirForPreparation.loadFontFileMap(); + dirForPreparation.update( + Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE))); + try { + dirForPreparation.update(Arrays.asList( + newFontUpdateRequest("foo,2", GOOD_SIGNATURE), + newFontUpdateRequest("bar,2", "Invalid signature"))); + fail("Batch update with invalid signature should fail"); + } catch (FontManagerService.SystemFontException e) { + // Expected + } + + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); + dir.loadFontFileMap(); + // The state should be rolled back as a whole if one of the update requests fail. + assertThat(dir.getFontFileMap()).containsKey("foo.ttf"); + assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1); + } + @Test public void installFontFile() throws Exception { FakeFontFileParser parser = new FakeFontFileParser(); @@ -290,7 +324,7 @@ public final class UpdatableFontDirTest { mConfigFile); dir.loadFontFileMap(); - installFontFile(dir, "test,1", GOOD_SIGNATURE); + dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE))); assertThat(dir.getFontFileMap()).containsKey("test.ttf"); assertThat(parser.getRevision(dir.getFontFileMap().get("test.ttf"))).isEqualTo(1); File fontFile = dir.getFontFileMap().get("test.ttf"); @@ -308,9 +342,9 @@ public final class UpdatableFontDirTest { mConfigFile); dir.loadFontFileMap(); - installFontFile(dir, "test,1", GOOD_SIGNATURE); + dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE))); Map mapBeforeUpgrade = dir.getFontFileMap(); - installFontFile(dir, "test,2", GOOD_SIGNATURE); + dir.update(Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE))); assertThat(dir.getFontFileMap()).containsKey("test.ttf"); assertThat(parser.getRevision(dir.getFontFileMap().get("test.ttf"))).isEqualTo(2); assertThat(mapBeforeUpgrade).containsKey("test.ttf"); @@ -327,9 +361,9 @@ public final class UpdatableFontDirTest { mConfigFile); dir.loadFontFileMap(); - installFontFile(dir, "test,2", GOOD_SIGNATURE); + dir.update(Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE))); try { - installFontFile(dir, "test,1", GOOD_SIGNATURE); + dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE))); fail("Expect IllegalArgumentException"); } catch (FontManagerService.SystemFontException e) { assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING); @@ -348,8 +382,26 @@ public final class UpdatableFontDirTest { mConfigFile); dir.loadFontFileMap(); - installFontFile(dir, "foo,1", GOOD_SIGNATURE); - installFontFile(dir, "bar,2", GOOD_SIGNATURE); + dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE))); + dir.update(Collections.singletonList(newFontUpdateRequest("bar,2", GOOD_SIGNATURE))); + assertThat(dir.getFontFileMap()).containsKey("foo.ttf"); + assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1); + assertThat(dir.getFontFileMap()).containsKey("bar.ttf"); + assertThat(parser.getRevision(dir.getFontFileMap().get("bar.ttf"))).isEqualTo(2); + } + + @Test + public void installFontFile_batch() throws Exception { + FakeFontFileParser parser = new FakeFontFileParser(); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); + dir.loadFontFileMap(); + + dir.update(Arrays.asList( + newFontUpdateRequest("foo,1", GOOD_SIGNATURE), + newFontUpdateRequest("bar,2", GOOD_SIGNATURE))); assertThat(dir.getFontFileMap()).containsKey("foo.ttf"); assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1); assertThat(dir.getFontFileMap()).containsKey("bar.ttf"); @@ -366,7 +418,8 @@ public final class UpdatableFontDirTest { dir.loadFontFileMap(); try { - installFontFile(dir, "test,1", "Invalid signature"); + dir.update( + Collections.singletonList(newFontUpdateRequest("test,1", "Invalid signature"))); fail("Expect SystemFontException"); } catch (FontManagerService.SystemFontException e) { assertThat(e.getErrorCode()) @@ -386,7 +439,7 @@ public final class UpdatableFontDirTest { dir.loadFontFileMap(); try { - installFontFile(dir, "test,1", GOOD_SIGNATURE); + dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE))); fail("Expect IllegalArgumentException"); } catch (FontManagerService.SystemFontException e) { assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING); @@ -417,7 +470,8 @@ public final class UpdatableFontDirTest { dir.loadFontFileMap(); try { - installFontFile(dir, "test,2", GOOD_SIGNATURE); + dir.update( + Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE))); } catch (FontManagerService.SystemFontException e) { assertThat(e.getErrorCode()) .isEqualTo(FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG); @@ -449,7 +503,7 @@ public final class UpdatableFontDirTest { dir.loadFontFileMap(); try { - installFontFile(dir, "foo,1", GOOD_SIGNATURE); + dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE))); fail("Expect SystemFontException"); } catch (FontManagerService.SystemFontException e) { assertThat(e.getErrorCode()) @@ -477,7 +531,7 @@ public final class UpdatableFontDirTest { dir.loadFontFileMap(); try { - installFontFile(dir, "foo,1", GOOD_SIGNATURE); + dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE))); fail("Expect SystemFontException"); } catch (FontManagerService.SystemFontException e) { assertThat(e.getErrorCode()) @@ -513,7 +567,7 @@ public final class UpdatableFontDirTest { dir.loadFontFileMap(); try { - installFontFile(dir, "foo,1", GOOD_SIGNATURE); + dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE))); fail("Expect SystemFontException"); } catch (FontManagerService.SystemFontException e) { assertThat(e.getErrorCode()) @@ -522,13 +576,36 @@ public final class UpdatableFontDirTest { assertThat(dir.getFontFileMap()).isEmpty(); } - private void installFontFile(UpdatableFontDir dir, String content, String signature) + @Test + public void installFontFile_batchFailure() throws Exception { + FakeFontFileParser parser = new FakeFontFileParser(); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); + dir.loadFontFileMap(); + + dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE))); + try { + dir.update(Arrays.asList( + newFontUpdateRequest("foo,2", GOOD_SIGNATURE), + newFontUpdateRequest("bar,2", "Invalid signature"))); + fail("Batch update with invalid signature should fail"); + } catch (FontManagerService.SystemFontException e) { + // Expected + } + // The state should be rolled back as a whole if one of the update requests fail. + assertThat(dir.getFontFileMap()).containsKey("foo.ttf"); + assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1); + } + + private FontUpdateRequest newFontUpdateRequest(String content, String signature) throws Exception { File file = File.createTempFile("font", "ttf", mCacheDir); FileUtils.stringToFile(file, content); - try (FileInputStream in = new FileInputStream(file)) { - dir.installFontFile(in.getFD(), signature.getBytes()); - } + return new FontUpdateRequest( + ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY), + signature.getBytes()); } private void writeConfig(PersistentSystemFontConfig.Config config,