diff options
| author | 2020-11-10 17:05:00 -0800 | |
|---|---|---|
| committer | 2020-11-11 15:28:37 -0800 | |
| commit | c7a5b3a90db2f97c1d95332e9bb5b5c0f34084d7 (patch) | |
| tree | 2f4ef686ece3d2e158cb287f5c156b08afc7e2b9 | |
| parent | df08f349d3c82441c0e76a37fdcb9cdb6001487c (diff) | |
Support loading system font map from SharedMemory.
Bug: 172891184
Test: atest FrameworksCoreTests:TypefaceTest
Change-Id: Ibcf10ccb160d83f7d01188b612c615b093696202
| -rw-r--r-- | core/tests/coretests/src/android/graphics/TypefaceTest.java | 104 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Typeface.java | 188 |
2 files changed, 224 insertions, 68 deletions
diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java index cfed2cef9ce6..4393e9e71336 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java @@ -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); diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index c58e5f36cde3..d44cb9c6032c 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -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); |