Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 353937a6 authored by Kohsuke Yatoh's avatar Kohsuke Yatoh
Browse files

Use FontManager Java API in tests.

Thanks to commit 84383b5e, 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
parent f74e3b69
Loading
Loading
Loading
Loading
+91 −78
Original line number Diff line number Diff line
@@ -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> {
        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());
            });
        }
    }

    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: " + fontFileName);
            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<Boolean> 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()) {