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

Commit c7a5b3a9 authored by Kohsuke Yatoh's avatar Kohsuke Yatoh
Browse files

Support loading system font map from SharedMemory.

Bug: 172891184
Test: atest FrameworksCoreTests:TypefaceTest
Change-Id: Ibcf10ccb160d83f7d01188b612c615b093696202
parent df08f349
Loading
Loading
Loading
Loading
+85 −19
Original line number Diff line number Diff line
@@ -17,12 +17,14 @@
package android.graphics;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.fonts.SystemFonts;
import android.os.SharedMemory;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
@@ -35,9 +37,8 @@ import com.android.frameworks.coretests.R;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

@RunWith(AndroidJUnit4.class)
@@ -54,14 +55,33 @@ public class TypefaceTest {
        Typeface.create(Typeface.MONOSPACE, 0)
    };

    private static final int[] STYLES = {
        Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC,
    };

    @SmallTest
    @Test
    public void testBasic() {
        assertNotEquals("basic", 0, Typeface.DEFAULT.native_instance);
        assertNotEquals("basic", 0, Typeface.DEFAULT_BOLD.native_instance);
        assertNotEquals("basic", 0, Typeface.SANS_SERIF.native_instance);
        assertNotEquals("basic", 0, Typeface.SERIF.native_instance);
        assertNotEquals("basic", 0, Typeface.MONOSPACE.native_instance);
        assertEquals("basic", Typeface.NORMAL, Typeface.DEFAULT.getStyle());
        assertEquals("basic", Typeface.BOLD, Typeface.DEFAULT_BOLD.getStyle());
        assertEquals("basic", Typeface.NORMAL, Typeface.SANS_SERIF.getStyle());
        assertEquals("basic", Typeface.NORMAL, Typeface.SERIF.getStyle());
        assertEquals("basic", Typeface.NORMAL, Typeface.MONOSPACE.getStyle());
    }

    @SmallTest
    @Test
    public void testBasic() throws Exception {
        assertTrue("basic", Typeface.DEFAULT != null);
        assertTrue("basic", Typeface.DEFAULT_BOLD != null);
        assertTrue("basic", Typeface.SANS_SERIF != null);
        assertTrue("basic", Typeface.SERIF != null);
        assertTrue("basic", Typeface.MONOSPACE != null);
    public void testDefaults() {
        for (int style : STYLES) {
            String msg = "style = " + style;
            assertNotEquals(msg, 0, Typeface.defaultFromStyle(style).native_instance);
            assertEquals(msg, style, Typeface.defaultFromStyle(style).getStyle());
        }
    }

    @SmallTest
@@ -178,21 +198,67 @@ public class TypefaceTest {
    @SmallTest
    @Test
    public void testSerialize() throws Exception {
        int size = Typeface.writeTypefaces(null, Arrays.asList(mFaces));
        ByteBuffer buffer = ByteBuffer.allocateDirect(size);
        Typeface.writeTypefaces(buffer, Arrays.asList(mFaces));
        List<Typeface> copiedTypefaces = Typeface.readTypefaces(buffer);
        assertNotNull(copiedTypefaces);
        assertEquals(mFaces.length, copiedTypefaces.size());
        for (int i = 0; i < mFaces.length; i++) {
            Typeface original = mFaces[i];
            Typeface copied = copiedTypefaces.get(i);
        HashMap<String, Typeface> systemFontMap = new HashMap<>();
        Typeface.initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(),
                SystemFonts.getAliases());
        SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap);
        Map<String, Typeface> copiedFontMap = Typeface.deserializeFontMap(sharedMemory);
        assertEquals(systemFontMap.size(), copiedFontMap.size());
        for (String key : systemFontMap.keySet()) {
            assertTrue(copiedFontMap.containsKey(key));
            Typeface original = systemFontMap.get(key);
            Typeface copied = copiedFontMap.get(key);
            assertEquals(original.getStyle(), copied.getStyle());
            assertEquals(original.getWeight(), copied.getWeight());
            assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6);
        }
    }

    @SmallTest
    @Test
    public void testSetSystemFontMap() throws Exception {
        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
        Resources res = context.getResources();
        Map<String, Typeface> fontMap = Map.of(
                "sans-serif", Typeface.create(res.getFont(R.font.samplefont), Typeface.NORMAL),
                "serif", Typeface.create(res.getFont(R.font.samplefont2), Typeface.NORMAL),
                "monospace", Typeface.create(res.getFont(R.font.samplefont3), Typeface.NORMAL),
                "sample", Typeface.create(res.getFont(R.font.samplefont4), Typeface.NORMAL),
                "sample-italic", Typeface.create(res.getFont(R.font.samplefont4), Typeface.ITALIC));
        Typeface.setSystemFontMap(fontMap);

        // Test public static final fields
        assertEquals(fontMap.get("sans-serif").native_instance, Typeface.DEFAULT.native_instance);
        assertNotEquals(0, Typeface.DEFAULT_BOLD.native_instance);
        assertEquals(
                fontMap.get("sans-serif").native_instance, Typeface.SANS_SERIF.native_instance);
        assertEquals(fontMap.get("serif").native_instance, Typeface.SERIF.native_instance);
        assertEquals(fontMap.get("monospace").native_instance, Typeface.MONOSPACE.native_instance);
        assertEquals(Typeface.NORMAL, Typeface.DEFAULT.getStyle());
        assertEquals(Typeface.BOLD, Typeface.DEFAULT_BOLD.getStyle());
        assertEquals(Typeface.NORMAL, Typeface.SANS_SERIF.getStyle());
        assertEquals(Typeface.NORMAL, Typeface.SERIF.getStyle());
        assertEquals(Typeface.NORMAL, Typeface.MONOSPACE.getStyle());

        // Test defaults
        assertEquals(
                fontMap.get("sans-serif").native_instance,
                Typeface.defaultFromStyle(Typeface.NORMAL).native_instance);
        for (int style : STYLES) {
            String msg = "style = " + style;
            assertNotEquals(msg, 0, Typeface.defaultFromStyle(style).native_instance);
            assertEquals(msg, style, Typeface.defaultFromStyle(style).getStyle());
        }

        // Test create()
        assertEquals(
                fontMap.get("sample").native_instance,
                Typeface.create("sample", Typeface.NORMAL).native_instance);
        assertEquals(
                fontMap.get("sample-italic").native_instance,
                Typeface.create("sample-italic", Typeface.ITALIC).native_instance);
    }

    private static float measureText(Typeface typeface, String text) {
        Paint paint = new Paint();
        paint.setTypeface(typeface);
+139 −49
Original line number Diff line number Diff line
@@ -34,8 +34,12 @@ import android.graphics.fonts.FontVariationAxis;
import android.graphics.fonts.SystemFonts;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.SharedMemory;
import android.os.Trace;
import android.provider.FontRequest;
import android.provider.FontsContract;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.text.FontConfig;
import android.util.Base64;
import android.util.LongSparseArray;
@@ -50,6 +54,7 @@ import dalvik.annotation.optimization.CriticalNative;

import libcore.util.NativeAllocationRegistry;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -57,6 +62,7 @@ import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -79,19 +85,19 @@ public class Typeface {
            Typeface.class.getClassLoader(), nativeGetReleaseFunc());

    /** The default NORMAL typeface object */
    public static final Typeface DEFAULT;
    public static final Typeface DEFAULT = new Typeface();
    /**
     * The default BOLD typeface object. Note: this may be not actually be
     * bold, depending on what fonts are installed. Call getStyle() to know
     * for sure.
     */
    public static final Typeface DEFAULT_BOLD;
    public static final Typeface DEFAULT_BOLD = new Typeface();
    /** The NORMAL style of the default sans serif typeface. */
    public static final Typeface SANS_SERIF;
    public static final Typeface SANS_SERIF = new Typeface();
    /** The NORMAL style of the default serif typeface. */
    public static final Typeface SERIF;
    public static final Typeface SERIF = new Typeface();
    /** The NORMAL style of the default monospace typeface. */
    public static final Typeface MONOSPACE;
    public static final Typeface MONOSPACE = new Typeface();

    /**
     * The default {@link Typeface}s for different text styles.
@@ -133,7 +139,7 @@ public class Typeface {
     * Use public API {@link #create(String, int)} to get the typeface for given familyName.
     */
    @UnsupportedAppUsage(trackingBug = 123769347)
    static final Map<String, Typeface> sSystemFontMap;
    static final Map<String, Typeface> sSystemFontMap = new HashMap<>();

    // We cannot support sSystemFallbackMap since we will migrate to public FontFamily API.
    /**
@@ -150,6 +156,9 @@ public class Typeface {
    @UnsupportedAppUsage
    public long native_instance;

    // This destructs native_instance.
    private Runnable mCleaner;

    /** @hide */
    @IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC})
    @Retention(RetentionPolicy.SOURCE)
@@ -1086,19 +1095,41 @@ public class Typeface {
        return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
    }

    /**
     * Creates a fake Typeface object that are later modified to point to another Typeface object
     * using {@link #reset(Typeface, int)}.
     *
     * <p>This constructor is only for filling 'static final' Typeface instances in Zygote process.
     */
    private Typeface() {
    }

    // don't allow clients to call this directly
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    private Typeface(long ni) {
        init(ni);
    }

    private void init(long ni) {
        if (ni == 0) {
            throw new RuntimeException("native typeface cannot be made");
        }

        if (mCleaner != null) {
            mCleaner.run();
        }
        native_instance = ni;
        sRegistry.registerNativeAllocation(this, native_instance);
        mCleaner = sRegistry.registerNativeAllocation(this, native_instance);
        mStyle = nativeGetStyle(ni);
        mWeight = nativeGetWeight(ni);
    }

    private void reset(Typeface typeface, int style) {
        // Just create a new native instance without looking into the cache, because we are going to
        // claim the ownership.
        long ni = nativeCreateFromTypeface(typeface.native_instance, style);
        init(ni);
    }

    private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
        Typeface tf = sSystemFontMap.get(familyName);
        return tf == null ? Typeface.DEFAULT : tf;
@@ -1137,23 +1168,105 @@ public class Typeface {
        }
    }

    static {
        final HashMap<String, Typeface> systemFontMap = new HashMap<>();
        initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(),
                SystemFonts.getAliases());
        sSystemFontMap = Collections.unmodifiableMap(systemFontMap);
    /** @hide */
    public static SharedMemory serializeFontMap(Map<String, Typeface> fontMap)
            throws IOException, ErrnoException {
        long[] nativePtrs = new long[fontMap.size()];
        // The name table will not be large, so let's create a byte array in memory.
        ByteArrayOutputStream namesBytes = new ByteArrayOutputStream();
        int i = 0;
        for (Map.Entry<String, Typeface> entry : fontMap.entrySet()) {
            nativePtrs[i++] = entry.getValue().native_instance;
            writeString(namesBytes, entry.getKey());
        }
        int typefacesBytesCount = nativeWriteTypefaces(null, nativePtrs);
        // int (typefacesBytesCount), typefaces, namesBytes
        SharedMemory sharedMemory = SharedMemory.create(
                "fontMap", Integer.BYTES + typefacesBytesCount + namesBytes.size());
        ByteBuffer writableBuffer = sharedMemory.mapReadWrite().order(ByteOrder.BIG_ENDIAN);
        try {
            writableBuffer.putInt(typefacesBytesCount);
            int writtenBytesCount = nativeWriteTypefaces(writableBuffer.slice(), nativePtrs);
            if (writtenBytesCount != typefacesBytesCount) {
                throw new IOException(String.format("Unexpected bytes written: %d, expected: %d",
                        writtenBytesCount, typefacesBytesCount));
            }
            writableBuffer.position(writableBuffer.position() + writtenBytesCount);
            writableBuffer.put(namesBytes.toByteArray());
        } finally {
            SharedMemory.unmap(writableBuffer);
        }
        sharedMemory.setProtect(OsConstants.PROT_READ);
        return sharedMemory;
    }

    /** @hide */
    @VisibleForTesting
    public static Map<String, Typeface> deserializeFontMap(SharedMemory sharedMemory)
            throws IOException, ErrnoException {
        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "deserializeFontMap");
        try {
            // TODO: unmap buffer when all Typefaces are gone.
            // Currently deserializeFontMap() should be called at most once per process, so we don't
            // need to unmap this buffer.
            ByteBuffer buffer = sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN);
            Map<String, Typeface> fontMap = new HashMap<>();
            int typefacesBytesCount = buffer.getInt();
            long[] nativePtrs = nativeReadTypefaces(buffer.slice());
            if (nativePtrs == null) {
                throw new IOException("Could not read typefaces");
            }
            buffer.position(buffer.position() + typefacesBytesCount);
            for (long nativePtr : nativePtrs) {
                String name = readString(buffer);
                fontMap.put(name, new Typeface(nativePtr));
            }
            return fontMap;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
        }
    }

    private static String readString(ByteBuffer buffer) {
        int length = buffer.getInt();
        byte[] bytes = new byte[length];
        buffer.get(bytes);
        return new String(bytes);
    }

    private static void writeString(ByteArrayOutputStream bos, String value) throws IOException {
        byte[] bytes = value.getBytes();
        writeInt(bos, bytes.length);
        bos.write(bytes);
    }

    private static void writeInt(ByteArrayOutputStream bos, int value) {
        // Write in the big endian order.
        bos.write((value >> 24) & 0xFF);
        bos.write((value >> 16) & 0xFF);
        bos.write((value >> 8) & 0xFF);
        bos.write(value & 0xFF);
    }

    /** @hide */
    @VisibleForTesting
    public static void setSystemFontMap(Map<String, Typeface> systemFontMap) {
        sSystemFontMap.clear();
        sSystemFontMap.putAll(systemFontMap);

        // We can't assume DEFAULT_FAMILY available on Roboletric.
        if (sSystemFontMap.containsKey(DEFAULT_FAMILY)) {
            setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
        }

        // Set up defaults and typefaces exposed in public API
        DEFAULT         = create((String) null, 0);
        DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
        SANS_SERIF      = create("sans-serif", 0);
        SERIF           = create("serif", 0);
        MONOSPACE       = create("monospace", 0);
        // Set up defaults and typefaces exposed in public API.
        // We cannot use getSystemDefaultTypeface() here to initialize DEFAULT, because it returns
        // DEFAULT.
        DEFAULT.reset(sDefaultTypeface, Typeface.NORMAL);
        DEFAULT_BOLD.reset(sDefaultTypeface, Typeface.BOLD);
        SANS_SERIF.reset(getSystemDefaultTypeface("sans-serif"), Typeface.NORMAL);
        SERIF.reset(getSystemDefaultTypeface("serif"), Typeface.NORMAL);
        MONOSPACE.reset(getSystemDefaultTypeface("monospace"), Typeface.NORMAL);

        sDefaults = new Typeface[] {
            DEFAULT,
@@ -1173,6 +1286,13 @@ public class Typeface {
        }
    }

    static {
        final HashMap<String, Typeface> systemFontMap = new HashMap<>();
        initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(),
                SystemFonts.getAliases());
        setSystemFontMap(systemFontMap);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
@@ -1210,36 +1330,6 @@ public class Typeface {
        return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
    }

    /**
     * Writes Typeface instances to the ByteBuffer and returns the number of bytes written.
     *
     * <p>If {@code buffer} is null, this method returns the number of bytes required to serialize
     * the typefaces, without writing anything.
     * @hide
     */
    public static int writeTypefaces(
            @Nullable ByteBuffer buffer, @NonNull List<Typeface> typefaces) {
        long[] nativePtrs = new long[typefaces.size()];
        for (int i = 0; i < nativePtrs.length; i++) {
            nativePtrs[i] = typefaces.get(i).native_instance;
        }
        return nativeWriteTypefaces(buffer, nativePtrs);
    }

    /**
     * Reads serialized Typeface instances from the ByteBuffer. Returns null on errors.
     * @hide
     */
    public static @Nullable List<Typeface> readTypefaces(@NonNull ByteBuffer buffer) {
        long[] nativePtrs = nativeReadTypefaces(buffer);
        if (nativePtrs == null) return null;
        List<Typeface> typefaces = new ArrayList<>(nativePtrs.length);
        for (long nativePtr : nativePtrs) {
            typefaces.add(new Typeface(nativePtr));
        }
        return typefaces;
    }

    private static native long nativeCreateFromTypeface(long native_instance, int style);
    private static native long nativeCreateFromTypefaceWithExactStyle(
            long native_instance, int weight, boolean italic);