diff options
| -rw-r--r-- | core/tests/coretests/src/android/graphics/TypefaceTest.java | 91 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Typeface.java | 112 |
2 files changed, 155 insertions, 48 deletions
diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java index c8ce884cf826..b0c7976f8014 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java @@ -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[] { @@ -36,8 +50,9 @@ public class TypefaceTest extends TestCase { Typeface.create(Typeface.SERIF, 3), Typeface.create(Typeface.MONOSPACE, 0) }; - + @SmallTest + @Test public void testBasic() throws Exception { assertTrue("basic", Typeface.DEFAULT != null); assertTrue("basic", Typeface.DEFAULT_BOLD != null); @@ -45,8 +60,9 @@ public class TypefaceTest extends TestCase { assertTrue("basic", Typeface.SERIF != null); assertTrue("basic", Typeface.MONOSPACE != null); } - + @SmallTest + @Test public void testUnique() throws Exception { final int n = mFaces.length; for (int i = 0; i < n; i++) { @@ -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); @@ -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; @@ -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 + } + } + + } + } diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 7496fa6abf47..cfc389f00436 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -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 @@ -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; @@ -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; @@ -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 */, @@ -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); @@ -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); @@ -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; @@ -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(); @@ -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. @@ -673,36 +694,37 @@ 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; + } + if (family == null) { + family = sDefaultTypeface; } - long ni = 0; - if (family != null) { - // Return early if we're asked for the same face/style - if (family.mStyle == style) { - return family; - } - ni = family.native_instance; + // Return early if we're asked for the same face/style + if (family.mStyle == style) { + return family; } + 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) { - typeface = styles.get(style); - if (typeface != null) { - return typeface; + if (styles == null) { + styles = new SparseArray<Typeface>(4); + sStyledTypefaceCache.put(ni, styles); + } else { + typeface = styles.get(style); + if (typeface != null) { + return typeface; + } } - } - typeface = new Typeface(nativeCreateFromTypeface(ni, style)); - if (styles == null) { - styles = new SparseArray<Typeface>(4); - sTypefaceCache.put(ni, styles); + typeface = new Typeface(nativeCreateFromTypeface(ni, style)); + styles.put(style, typeface); } - styles.put(style, typeface); - return typeface; } @@ -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. @@ -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; @@ -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; @@ -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; |