Loading core/tests/coretests/src/android/graphics/TypefaceTest.java +87 −4 Original line number Diff line number Diff line Loading @@ -16,15 +16,29 @@ package android.graphics; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.content.Context; import android.content.res.AssetManager; import android.content.res.Resources; import android.graphics.Paint; import android.graphics.Typeface; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import junit.framework.TestCase; import com.android.frameworks.coretests.R; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Random; public class TypefaceTest extends TestCase { @RunWith(AndroidJUnit4.class) public class TypefaceTest { // create array of all std faces private final Typeface[] mFaces = new Typeface[] { Loading @@ -38,6 +52,7 @@ public class TypefaceTest extends TestCase { }; @SmallTest @Test public void testBasic() throws Exception { assertTrue("basic", Typeface.DEFAULT != null); assertTrue("basic", Typeface.DEFAULT_BOLD != null); Loading @@ -47,6 +62,7 @@ public class TypefaceTest extends TestCase { } @SmallTest @Test public void testUnique() throws Exception { final int n = mFaces.length; for (int i = 0; i < n; i++) { Loading @@ -57,6 +73,7 @@ public class TypefaceTest extends TestCase { } @SmallTest @Test public void testStyles() throws Exception { assertTrue("style", mFaces[0].getStyle() == Typeface.NORMAL); assertTrue("style", mFaces[1].getStyle() == Typeface.BOLD); Loading @@ -68,6 +85,7 @@ public class TypefaceTest extends TestCase { } @MediumTest @Test public void testUniformY() throws Exception { Paint p = new Paint(); final int n = mFaces.length; Loading @@ -89,4 +107,69 @@ public class TypefaceTest extends TestCase { } } @LargeTest @Test public void testMultithreadCacheStressTest() { final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); final Resources res = context.getResources(); final AssetManager assets = res.getAssets(); final Typeface[] baseTypefaces = { null, Typeface.SANS_SERIF, Typeface.SERIF, Typeface.MONOSPACE, res.getFont(R.font.samplefont), res.getFont(R.font.samplefont2), res.getFont(R.font.samplefont3), res.getFont(R.font.samplefont4), res.getFont(R.font.samplexmlfont), Typeface.createFromAsset(assets, "fonts/a3em.ttf"), Typeface.createFromAsset(assets, "fonts/b3em.ttf"), Typeface.createFromAsset(assets, "fonts/c3em.ttf"), Typeface.createFromAsset(assets, "fonts/all2em.ttf"), Typeface.createFromAsset(assets, "fonts/hasGlyphTestFont.ttf"), Typeface.createFromAsset(assets, "fonts/samplefont1.ttf"), Typeface.createFromAsset(assets, "fonts/no_coverage.ttf"), }; final int loopCount = 10000; final Runnable threadedCreater = () -> { final Random random = new Random(); for (int i = 0; i < loopCount; ++i) { final Typeface base = baseTypefaces[random.nextInt(baseTypefaces.length)]; if (random.nextBoolean()) { final int style = random.nextInt(3); final Typeface result = Typeface.create(base, style); assertEquals(style, result.getStyle()); } else { final int weight = 100 * (random.nextInt(10) + 1); // [100, 1000] final boolean italic = random.nextBoolean(); final Typeface result = Typeface.create(base, weight, italic); assertEquals(italic, result.isItalic()); assertEquals(weight, result.getWeight()); } } }; final int threadCount = 4; final Thread[] threads = new Thread[threadCount]; for (int i = 0; i < threadCount; ++i) { threads[i] = new Thread(threadedCreater); } for (int i = 0; i < threadCount; ++i) { threads[i].start(); } for (int i = 0; i < threadCount; ++i) { try { threads[i].join(); } catch (InterruptedException e) { // ignore } } } } graphics/java/android/graphics/Typeface.java +68 −44 Original line number Diff line number Diff line Loading @@ -97,19 +97,33 @@ public class Typeface { public static final Typeface MONOSPACE; static Typeface[] sDefaults; private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache = /** * Cache for Typeface objects for style variant. Currently max size is 3. */ @GuardedBy("sStyledCacheLock") private static final LongSparseArray<SparseArray<Typeface>> sStyledTypefaceCache = new LongSparseArray<>(3); private static final Object sStyledCacheLock = new Object(); /** * Cache for Typeface objects for weight variant. Currently max size is 3. */ @GuardedBy("sWeightCacheLock") private static final LongSparseArray<SparseArray<Typeface>> sWeightTypefaceCache = new LongSparseArray<>(3); private static final Object sWeightCacheLock = new Object(); /** * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16. */ @GuardedBy("sLock") @GuardedBy("sDynamicCacheLock") private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16); private static final Object sDynamicCacheLock = new Object(); static Typeface sDefaultTypeface; static final Map<String, Typeface> sSystemFontMap; static final Map<String, FontFamily[]> sSystemFallbackMap; private static final Object sLock = new Object(); /** * @hide Loading @@ -121,6 +135,7 @@ public class Typeface { public static final int BOLD = 1; public static final int ITALIC = 2; public static final int BOLD_ITALIC = 3; /** @hide */ public static final int STYLE_MASK = 0x03; private int mStyle = 0; private int mWeight = 0; Loading @@ -143,6 +158,13 @@ public class Typeface { nativeSetDefault(t.native_instance); } // TODO: Make this public API. (b/64852739) /** @hide */ @VisibleForTesting public int getWeight() { return mWeight; } /** Returns the typeface's intrinsic style attributes */ public int getStyle() { return mStyle; Loading @@ -164,7 +186,7 @@ public class Typeface { */ @Nullable public static Typeface createFromResources(AssetManager mgr, String path, int cookie) { synchronized (sDynamicTypefaceCache) { synchronized (sDynamicCacheLock) { final String key = Builder.createAssetUid( mgr, path, 0 /* ttcIndex */, null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */, Loading Loading @@ -241,7 +263,7 @@ public class Typeface { FontFamily[] familyChain = { fontFamily }; typeface = createFromFamiliesWithDefault(familyChain, DEFAULT_FAMILY, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); synchronized (sDynamicTypefaceCache) { synchronized (sDynamicCacheLock) { final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY); Loading @@ -255,7 +277,7 @@ public class Typeface { * @hide */ public static Typeface findFromCache(AssetManager mgr, String path) { synchronized (sDynamicTypefaceCache) { synchronized (sDynamicCacheLock) { final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY); Loading Loading @@ -525,12 +547,6 @@ public class Typeface { return builder.toString(); } private static final Object sLock = new Object(); // TODO: Unify with Typeface.sTypefaceCache. @GuardedBy("sLock") private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache = new LongSparseArray<>(3); private Typeface resolveFallbackTypeface() { if (mFallbackFamilyName == null) { return null; Loading Loading @@ -581,7 +597,7 @@ public class Typeface { final String key = createAssetUid( mAssetManager, mPath, mTtcIndex, mAxes, mWeight, mItalic, mFallbackFamilyName); synchronized (sLock) { synchronized (sDynamicCacheLock) { Typeface typeface = sDynamicTypefaceCache.get(key); if (typeface != null) return typeface; final FontFamily fontFamily = new FontFamily(); Loading Loading @@ -666,6 +682,11 @@ public class Typeface { * style from the same family of an existing typeface object. If family is * null, this selects from the default font's family. * * <p> * This method is not thread safe on API 27 or before. * This method is thread safe on API 28 or after. * </p> * * @param family An existing {@link Typeface} object. In case of {@code null}, the default * typeface is used instead. * @param style The style (normal, bold, italic) of the typeface. Loading @@ -673,23 +694,28 @@ public class Typeface { * @return The best matching typeface. */ public static Typeface create(Typeface family, int style) { if (style < 0 || style > 3) { style = 0; if ((style & ~STYLE_MASK) != 0) { style = NORMAL; } long ni = 0; if (family != null) { if (family == null) { family = sDefaultTypeface; } // Return early if we're asked for the same face/style if (family.mStyle == style) { return family; } ni = family.native_instance; } final long ni = family.native_instance; Typeface typeface; SparseArray<Typeface> styles = sTypefaceCache.get(ni); synchronized (sStyledCacheLock) { SparseArray<Typeface> styles = sStyledTypefaceCache.get(ni); if (styles != null) { if (styles == null) { styles = new SparseArray<Typeface>(4); sStyledTypefaceCache.put(ni, styles); } else { typeface = styles.get(style); if (typeface != null) { return typeface; Loading @@ -697,12 +723,8 @@ public class Typeface { } typeface = new Typeface(nativeCreateFromTypeface(ni, style)); if (styles == null) { styles = new SparseArray<Typeface>(4); sTypefaceCache.put(ni, styles); } styles.put(style, typeface); } return typeface; } Loading @@ -710,6 +732,10 @@ public class Typeface { * Creates a typeface object that best matches the specified existing typeface and the specified * weight and italic style * * <p> * This method is thread safe. * </p> * * @param family An existing {@link Typeface} object. In case of {@code null}, the default * typeface is used instead. * @param weight The desired weight to be drawn. Loading @@ -728,12 +754,15 @@ public class Typeface { private static @NonNull Typeface createWeightStyle(@NonNull Typeface base, @IntRange(from = 1, to = 1000) int weight, boolean italic) { final int key = weight << 1 | (italic ? 1 : 0); final int key = (weight << 1) | (italic ? 1 : 0); Typeface typeface; synchronized(sLock) { SparseArray<Typeface> innerCache = sTypefaceCache.get(base.native_instance); if (innerCache != null) { synchronized(sWeightCacheLock) { SparseArray<Typeface> innerCache = sWeightTypefaceCache.get(base.native_instance); if (innerCache == null) { innerCache = new SparseArray<>(4); sWeightTypefaceCache.put(base.native_instance, innerCache); } else { typeface = innerCache.get(key); if (typeface != null) { return typeface; Loading @@ -743,11 +772,6 @@ public class Typeface { typeface = new Typeface( nativeCreateFromTypefaceWithExactStyle( base.native_instance, weight, italic)); if (innerCache == null) { innerCache = new SparseArray<>(4); // [regular, bold] x [upright, italic] sTypefaceCache.put(base.native_instance, innerCache); } innerCache.put(key, typeface); } return typeface; Loading Loading @@ -780,7 +804,7 @@ public class Typeface { if (path == null) { throw new NullPointerException(); // for backward compatibility } synchronized (sLock) { synchronized (sDynamicCacheLock) { Typeface typeface = new Builder(mgr, path).build(); if (typeface != null) return typeface; Loading Loading
core/tests/coretests/src/android/graphics/TypefaceTest.java +87 −4 Original line number Diff line number Diff line Loading @@ -16,15 +16,29 @@ package android.graphics; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.content.Context; import android.content.res.AssetManager; import android.content.res.Resources; import android.graphics.Paint; import android.graphics.Typeface; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import junit.framework.TestCase; import com.android.frameworks.coretests.R; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Random; public class TypefaceTest extends TestCase { @RunWith(AndroidJUnit4.class) public class TypefaceTest { // create array of all std faces private final Typeface[] mFaces = new Typeface[] { Loading @@ -38,6 +52,7 @@ public class TypefaceTest extends TestCase { }; @SmallTest @Test public void testBasic() throws Exception { assertTrue("basic", Typeface.DEFAULT != null); assertTrue("basic", Typeface.DEFAULT_BOLD != null); Loading @@ -47,6 +62,7 @@ public class TypefaceTest extends TestCase { } @SmallTest @Test public void testUnique() throws Exception { final int n = mFaces.length; for (int i = 0; i < n; i++) { Loading @@ -57,6 +73,7 @@ public class TypefaceTest extends TestCase { } @SmallTest @Test public void testStyles() throws Exception { assertTrue("style", mFaces[0].getStyle() == Typeface.NORMAL); assertTrue("style", mFaces[1].getStyle() == Typeface.BOLD); Loading @@ -68,6 +85,7 @@ public class TypefaceTest extends TestCase { } @MediumTest @Test public void testUniformY() throws Exception { Paint p = new Paint(); final int n = mFaces.length; Loading @@ -89,4 +107,69 @@ public class TypefaceTest extends TestCase { } } @LargeTest @Test public void testMultithreadCacheStressTest() { final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); final Resources res = context.getResources(); final AssetManager assets = res.getAssets(); final Typeface[] baseTypefaces = { null, Typeface.SANS_SERIF, Typeface.SERIF, Typeface.MONOSPACE, res.getFont(R.font.samplefont), res.getFont(R.font.samplefont2), res.getFont(R.font.samplefont3), res.getFont(R.font.samplefont4), res.getFont(R.font.samplexmlfont), Typeface.createFromAsset(assets, "fonts/a3em.ttf"), Typeface.createFromAsset(assets, "fonts/b3em.ttf"), Typeface.createFromAsset(assets, "fonts/c3em.ttf"), Typeface.createFromAsset(assets, "fonts/all2em.ttf"), Typeface.createFromAsset(assets, "fonts/hasGlyphTestFont.ttf"), Typeface.createFromAsset(assets, "fonts/samplefont1.ttf"), Typeface.createFromAsset(assets, "fonts/no_coverage.ttf"), }; final int loopCount = 10000; final Runnable threadedCreater = () -> { final Random random = new Random(); for (int i = 0; i < loopCount; ++i) { final Typeface base = baseTypefaces[random.nextInt(baseTypefaces.length)]; if (random.nextBoolean()) { final int style = random.nextInt(3); final Typeface result = Typeface.create(base, style); assertEquals(style, result.getStyle()); } else { final int weight = 100 * (random.nextInt(10) + 1); // [100, 1000] final boolean italic = random.nextBoolean(); final Typeface result = Typeface.create(base, weight, italic); assertEquals(italic, result.isItalic()); assertEquals(weight, result.getWeight()); } } }; final int threadCount = 4; final Thread[] threads = new Thread[threadCount]; for (int i = 0; i < threadCount; ++i) { threads[i] = new Thread(threadedCreater); } for (int i = 0; i < threadCount; ++i) { threads[i].start(); } for (int i = 0; i < threadCount; ++i) { try { threads[i].join(); } catch (InterruptedException e) { // ignore } } } }
graphics/java/android/graphics/Typeface.java +68 −44 Original line number Diff line number Diff line Loading @@ -97,19 +97,33 @@ public class Typeface { public static final Typeface MONOSPACE; static Typeface[] sDefaults; private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache = /** * Cache for Typeface objects for style variant. Currently max size is 3. */ @GuardedBy("sStyledCacheLock") private static final LongSparseArray<SparseArray<Typeface>> sStyledTypefaceCache = new LongSparseArray<>(3); private static final Object sStyledCacheLock = new Object(); /** * Cache for Typeface objects for weight variant. Currently max size is 3. */ @GuardedBy("sWeightCacheLock") private static final LongSparseArray<SparseArray<Typeface>> sWeightTypefaceCache = new LongSparseArray<>(3); private static final Object sWeightCacheLock = new Object(); /** * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16. */ @GuardedBy("sLock") @GuardedBy("sDynamicCacheLock") private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16); private static final Object sDynamicCacheLock = new Object(); static Typeface sDefaultTypeface; static final Map<String, Typeface> sSystemFontMap; static final Map<String, FontFamily[]> sSystemFallbackMap; private static final Object sLock = new Object(); /** * @hide Loading @@ -121,6 +135,7 @@ public class Typeface { public static final int BOLD = 1; public static final int ITALIC = 2; public static final int BOLD_ITALIC = 3; /** @hide */ public static final int STYLE_MASK = 0x03; private int mStyle = 0; private int mWeight = 0; Loading @@ -143,6 +158,13 @@ public class Typeface { nativeSetDefault(t.native_instance); } // TODO: Make this public API. (b/64852739) /** @hide */ @VisibleForTesting public int getWeight() { return mWeight; } /** Returns the typeface's intrinsic style attributes */ public int getStyle() { return mStyle; Loading @@ -164,7 +186,7 @@ public class Typeface { */ @Nullable public static Typeface createFromResources(AssetManager mgr, String path, int cookie) { synchronized (sDynamicTypefaceCache) { synchronized (sDynamicCacheLock) { final String key = Builder.createAssetUid( mgr, path, 0 /* ttcIndex */, null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */, Loading Loading @@ -241,7 +263,7 @@ public class Typeface { FontFamily[] familyChain = { fontFamily }; typeface = createFromFamiliesWithDefault(familyChain, DEFAULT_FAMILY, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); synchronized (sDynamicTypefaceCache) { synchronized (sDynamicCacheLock) { final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY); Loading @@ -255,7 +277,7 @@ public class Typeface { * @hide */ public static Typeface findFromCache(AssetManager mgr, String path) { synchronized (sDynamicTypefaceCache) { synchronized (sDynamicCacheLock) { final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY); Loading Loading @@ -525,12 +547,6 @@ public class Typeface { return builder.toString(); } private static final Object sLock = new Object(); // TODO: Unify with Typeface.sTypefaceCache. @GuardedBy("sLock") private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache = new LongSparseArray<>(3); private Typeface resolveFallbackTypeface() { if (mFallbackFamilyName == null) { return null; Loading Loading @@ -581,7 +597,7 @@ public class Typeface { final String key = createAssetUid( mAssetManager, mPath, mTtcIndex, mAxes, mWeight, mItalic, mFallbackFamilyName); synchronized (sLock) { synchronized (sDynamicCacheLock) { Typeface typeface = sDynamicTypefaceCache.get(key); if (typeface != null) return typeface; final FontFamily fontFamily = new FontFamily(); Loading Loading @@ -666,6 +682,11 @@ public class Typeface { * style from the same family of an existing typeface object. If family is * null, this selects from the default font's family. * * <p> * This method is not thread safe on API 27 or before. * This method is thread safe on API 28 or after. * </p> * * @param family An existing {@link Typeface} object. In case of {@code null}, the default * typeface is used instead. * @param style The style (normal, bold, italic) of the typeface. Loading @@ -673,23 +694,28 @@ public class Typeface { * @return The best matching typeface. */ public static Typeface create(Typeface family, int style) { if (style < 0 || style > 3) { style = 0; if ((style & ~STYLE_MASK) != 0) { style = NORMAL; } long ni = 0; if (family != null) { if (family == null) { family = sDefaultTypeface; } // Return early if we're asked for the same face/style if (family.mStyle == style) { return family; } ni = family.native_instance; } final long ni = family.native_instance; Typeface typeface; SparseArray<Typeface> styles = sTypefaceCache.get(ni); synchronized (sStyledCacheLock) { SparseArray<Typeface> styles = sStyledTypefaceCache.get(ni); if (styles != null) { if (styles == null) { styles = new SparseArray<Typeface>(4); sStyledTypefaceCache.put(ni, styles); } else { typeface = styles.get(style); if (typeface != null) { return typeface; Loading @@ -697,12 +723,8 @@ public class Typeface { } typeface = new Typeface(nativeCreateFromTypeface(ni, style)); if (styles == null) { styles = new SparseArray<Typeface>(4); sTypefaceCache.put(ni, styles); } styles.put(style, typeface); } return typeface; } Loading @@ -710,6 +732,10 @@ public class Typeface { * Creates a typeface object that best matches the specified existing typeface and the specified * weight and italic style * * <p> * This method is thread safe. * </p> * * @param family An existing {@link Typeface} object. In case of {@code null}, the default * typeface is used instead. * @param weight The desired weight to be drawn. Loading @@ -728,12 +754,15 @@ public class Typeface { private static @NonNull Typeface createWeightStyle(@NonNull Typeface base, @IntRange(from = 1, to = 1000) int weight, boolean italic) { final int key = weight << 1 | (italic ? 1 : 0); final int key = (weight << 1) | (italic ? 1 : 0); Typeface typeface; synchronized(sLock) { SparseArray<Typeface> innerCache = sTypefaceCache.get(base.native_instance); if (innerCache != null) { synchronized(sWeightCacheLock) { SparseArray<Typeface> innerCache = sWeightTypefaceCache.get(base.native_instance); if (innerCache == null) { innerCache = new SparseArray<>(4); sWeightTypefaceCache.put(base.native_instance, innerCache); } else { typeface = innerCache.get(key); if (typeface != null) { return typeface; Loading @@ -743,11 +772,6 @@ public class Typeface { typeface = new Typeface( nativeCreateFromTypefaceWithExactStyle( base.native_instance, weight, italic)); if (innerCache == null) { innerCache = new SparseArray<>(4); // [regular, bold] x [upright, italic] sTypefaceCache.put(base.native_instance, innerCache); } innerCache.put(key, typeface); } return typeface; Loading Loading @@ -780,7 +804,7 @@ public class Typeface { if (path == null) { throw new NullPointerException(); // for backward compatibility } synchronized (sLock) { synchronized (sDynamicCacheLock) { Typeface typeface = new Builder(mgr, path).build(); if (typeface != null) return typeface; Loading