From 353937a62b77a41f83469cbad4ed76bc604bce34 Mon Sep 17 00:00:00 2001 From: Kohsuke Yatoh Date: Mon, 3 May 2021 09:51:59 -0700 Subject: [PATCH] Use FontManager Java API in tests. Thanks to commit 84383b5ef87586804238c5dc62a2f274e7deb82e, now Shell has UPDATE_FONTS permission and we can use it in instrumentation tests via UiAutomation#adoptShellPermissionIdentity(). Bug: 186966067 Test: atest UpdatableSystemFontTest Change-Id: Idd2043f53d95b7c904d563dd3ef1d1846841c0d6 --- .../UpdatableSystemFontTest.java | 169 ++++++++++-------- 1 file changed, 91 insertions(+), 78 deletions(-) diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java index 79e23b8b5e462..9793c3447ff27 100644 --- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java +++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java @@ -16,6 +16,8 @@ package com.android.updatablesystemfont; +import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assume.assumeTrue; @@ -24,9 +26,13 @@ import static java.util.concurrent.TimeUnit.SECONDS; import android.app.UiAutomation; import android.content.Context; +import android.graphics.fonts.FontFamilyUpdateRequest; +import android.graphics.fonts.FontFileUpdateRequest; +import android.graphics.fonts.FontManager; import android.os.ParcelFileDescriptor; import android.platform.test.annotations.RootPermissionTest; import android.security.FileIntegrityManager; +import android.text.FontConfig; import android.util.Log; import android.util.Pair; @@ -35,22 +41,24 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; import com.android.compatibility.common.util.StreamUtil; +import com.android.compatibility.common.util.SystemUtil; import org.junit.After; import org.junit.Before; 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.io.InputStream; import java.io.OutputStream; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.nio.file.Files; +import java.nio.file.Paths; /** - * Tests if fonts can be updated by 'cmd font'. + * Tests if fonts can be updated by {@link FontManager} API. */ @RootPermissionTest @RunWith(AndroidJUnit4.class) @@ -59,11 +67,8 @@ public class UpdatableSystemFontTest { private static final String TAG = "UpdatableSystemFontTest"; private static final String SYSTEM_FONTS_DIR = "/system/fonts/"; private static final String DATA_FONTS_DIR = "/data/fonts/files/"; - private static final String CERT_PATH = "/data/local/tmp/UpdatableSystemFontTestCert.der"; - - private static final Pattern PATTERN_FONT = Pattern.compile("path = ([^, \n]*)"); - private static final String NOTO_COLOR_EMOJI_TTF = "NotoColorEmoji.ttf"; + private static final String NOTO_COLOR_EMOJI_POSTSCRIPT_NAME = "NotoColorEmoji"; private static final String ORIGINAL_NOTO_COLOR_EMOJI_TTF = "/data/local/tmp/NotoColorEmoji.ttf"; @@ -90,11 +95,8 @@ public class UpdatableSystemFontTest { EMOJI_RENDERING_TEST_APP_ID + "/.EmojiRenderingTestActivity"; private static final long ACTIVITY_TIMEOUT_MILLIS = SECONDS.toMillis(10); - private interface ThrowingSupplier { - T get() throws Exception; - } - private String mKeyId; + private FontManager mFontManager; @Before public void setUp() throws Exception { @@ -104,6 +106,7 @@ public class UpdatableSystemFontTest { assumeTrue(fim != null); assumeTrue(fim.isApkVeritySupported()); mKeyId = insertCert(CERT_PATH); + mFontManager = context.getSystemService(FontManager.class); expectCommandToSucceed("cmd font clear"); } @@ -117,9 +120,10 @@ public class UpdatableSystemFontTest { @Test public void updateFont() throws Exception { - expectCommandToSucceed(String.format("cmd font update %s %s", - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)); - String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF); + assertThat(updateFontFile( + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + .isEqualTo(FontManager.RESULT_SUCCESS); + String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath).startsWith(DATA_FONTS_DIR); // The updated font should be readable and unmodifiable. expectCommandToSucceed("dd status=none if=" + fontPath + " of=/dev/null"); @@ -128,12 +132,14 @@ public class UpdatableSystemFontTest { @Test public void updateFont_twice() throws Exception { - expectCommandToSucceed(String.format("cmd font update %s %s", - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)); - String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF); - expectCommandToSucceed(String.format("cmd font update %s %s", - TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG)); - String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_TTF); + assertThat(updateFontFile( + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + .isEqualTo(FontManager.RESULT_SUCCESS); + String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); + assertThat(updateFontFile( + TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG)) + .isEqualTo(FontManager.RESULT_SUCCESS); + String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath2).startsWith(DATA_FONTS_DIR); assertThat(fontPath2).isNotEqualTo(fontPath); // The new file should be readable. @@ -145,16 +151,19 @@ public class UpdatableSystemFontTest { @Test public void updateFont_allowSameVersion() throws Exception { // Update original font to the same version - expectCommandToSucceed(String.format("cmd font update %s %s", - ORIGINAL_NOTO_COLOR_EMOJI_TTF, ORIGINAL_NOTO_COLOR_EMOJI_TTF_FSV_SIG)); - String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF); - expectCommandToSucceed(String.format("cmd font update %s %s", - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)); - String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_TTF); + assertThat(updateFontFile( + ORIGINAL_NOTO_COLOR_EMOJI_TTF, ORIGINAL_NOTO_COLOR_EMOJI_TTF_FSV_SIG)) + .isEqualTo(FontManager.RESULT_SUCCESS); + String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); + assertThat(updateFontFile( + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + .isEqualTo(FontManager.RESULT_SUCCESS); + String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); // Update updated font to the same version - expectCommandToSucceed(String.format("cmd font update %s %s", - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)); - String fontPath3 = getFontPath(NOTO_COLOR_EMOJI_TTF); + assertThat(updateFontFile( + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + .isEqualTo(FontManager.RESULT_SUCCESS); + String fontPath3 = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath).startsWith(DATA_FONTS_DIR); assertThat(fontPath2).isNotEqualTo(fontPath); assertThat(fontPath2).startsWith(DATA_FONTS_DIR); @@ -164,58 +173,65 @@ public class UpdatableSystemFontTest { @Test public void updateFont_invalidCert() throws Exception { - expectCommandToFail(String.format("cmd font update %s %s", - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG)); + assertThat(updateFontFile( + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG)) + .isEqualTo(FontManager.RESULT_ERROR_VERIFICATION_FAILURE); } @Test public void updateFont_downgradeFromSystem() throws Exception { - expectCommandToFail(String.format("cmd font update %s %s", - TEST_NOTO_COLOR_EMOJI_V0_TTF, TEST_NOTO_COLOR_EMOJI_V0_TTF_FSV_SIG)); + assertThat(updateFontFile( + TEST_NOTO_COLOR_EMOJI_V0_TTF, TEST_NOTO_COLOR_EMOJI_V0_TTF_FSV_SIG)) + .isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING); } @Test public void updateFont_downgradeFromData() throws Exception { - expectCommandToSucceed(String.format("cmd font update %s %s", - TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG)); - expectCommandToFail(String.format("cmd font update %s %s", - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)); + assertThat(updateFontFile( + TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG)) + .isEqualTo(FontManager.RESULT_SUCCESS); + assertThat(updateFontFile( + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + .isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING); } @Test public void launchApp() throws Exception { - String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF); + String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath).startsWith(SYSTEM_FONTS_DIR); startActivity(EMOJI_RENDERING_TEST_APP_ID, EMOJI_RENDERING_TEST_ACTIVITY); - waitUntil(ACTIVITY_TIMEOUT_MILLIS, () -> - isFileOpenedBy(fontPath, EMOJI_RENDERING_TEST_APP_ID)); + SystemUtil.eventually( + () -> assertThat(isFileOpenedBy(fontPath, EMOJI_RENDERING_TEST_APP_ID)).isTrue(), + ACTIVITY_TIMEOUT_MILLIS); } @Test public void launchApp_afterUpdateFont() throws Exception { - String originalFontPath = getFontPath(NOTO_COLOR_EMOJI_TTF); + String originalFontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(originalFontPath).startsWith(SYSTEM_FONTS_DIR); - expectCommandToSucceed(String.format("cmd font update %s %s", - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)); - String updatedFontPath = getFontPath(NOTO_COLOR_EMOJI_TTF); + assertThat(updateFontFile( + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + .isEqualTo(FontManager.RESULT_SUCCESS); + String updatedFontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(updatedFontPath).startsWith(DATA_FONTS_DIR); startActivity(EMOJI_RENDERING_TEST_APP_ID, EMOJI_RENDERING_TEST_ACTIVITY); // The original font should NOT be opened by the app. - waitUntil(ACTIVITY_TIMEOUT_MILLIS, () -> - isFileOpenedBy(updatedFontPath, EMOJI_RENDERING_TEST_APP_ID) - && !isFileOpenedBy(originalFontPath, EMOJI_RENDERING_TEST_APP_ID)); + SystemUtil.eventually(() -> { + assertThat(isFileOpenedBy(updatedFontPath, EMOJI_RENDERING_TEST_APP_ID)).isTrue(); + assertThat(isFileOpenedBy(originalFontPath, EMOJI_RENDERING_TEST_APP_ID)).isFalse(); + }, ACTIVITY_TIMEOUT_MILLIS); } @Test public void reboot() throws Exception { expectCommandToSucceed(String.format("cmd font update %s %s", TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)); - String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF); + String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath).startsWith(DATA_FONTS_DIR); // Emulate reboot by 'cmd font restart'. expectCommandToSucceed("cmd font restart"); - String fontPathAfterReboot = getFontPath(NOTO_COLOR_EMOJI_TTF); + String fontPathAfterReboot = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPathAfterReboot).isEqualTo(fontPath); } @@ -231,24 +247,36 @@ public class UpdatableSystemFontTest { return keyId; } - private static String getFontPath(String fontFileName) throws Exception { - // TODO: add a dedicated command for testing. - String lines = expectCommandToSucceed("cmd font dump"); - for (String line : lines.split("\n")) { - Matcher m = PATTERN_FONT.matcher(line); - if (m.find() && m.group(1).endsWith(fontFileName)) { - return m.group(1); - } + private int updateFontFile(String fontPath, String signaturePath) throws IOException { + byte[] signature = Files.readAllBytes(Paths.get(signaturePath)); + try (ParcelFileDescriptor fd = + ParcelFileDescriptor.open(new File(fontPath), MODE_READ_ONLY)) { + return SystemUtil.runWithShellPermissionIdentity(() -> { + FontConfig fontConfig = mFontManager.getFontConfig(); + return mFontManager.updateFontFamily( + new FontFamilyUpdateRequest.Builder() + .addFontFileUpdateRequest(new FontFileUpdateRequest(fd, signature)) + .build(), + fontConfig.getConfigVersion()); + }); } - throw new AssertionError("Font not found: " + fontFileName); + } + + private String getFontPath(String psName) { + return SystemUtil.runWithShellPermissionIdentity(() -> { + FontConfig fontConfig = mFontManager.getFontConfig(); + for (FontConfig.FontFamily family : fontConfig.getFontFamilies()) { + for (FontConfig.Font font : family.getFontList()) { + if (psName.equals(font.getPostScriptName())) { + return font.getFile().getAbsolutePath(); + } + } + } + throw new AssertionError("Font not found: " + psName); + }); } private static void startActivity(String appId, String activityId) throws Exception { - // Make sure that the app is installed and enabled. - waitUntil(ACTIVITY_TIMEOUT_MILLIS, () -> { - String packageInfo = expectCommandToSucceed("pm list packages -e " + appId); - return !packageInfo.isEmpty(); - }); expectCommandToSucceed("am force-stop " + appId); expectCommandToSucceed("am start-activity -n " + activityId); } @@ -300,21 +328,6 @@ public class UpdatableSystemFontTest { } } - private static void waitUntil(long timeoutMillis, ThrowingSupplier func) { - long untilMillis = System.currentTimeMillis() + timeoutMillis; - do { - try { - if (func.get()) return; - Thread.sleep(100); - } catch (InterruptedException e) { - throw new AssertionError("Interrupted", e); - } catch (Exception e) { - throw new AssertionError("Unexpected exception", e); - } - } while (System.currentTimeMillis() < untilMillis); - throw new AssertionError("Timed out"); - } - private static boolean isFileOpenedBy(String path, String appId) throws Exception { String pid = pidOf(appId); if (pid.isEmpty()) {