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

Commit a14eb1ec authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Support loading system font map from SharedMemory."

parents aef17be9 c7a5b3a9
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);