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

Commit 4a40e0fe authored by Seigo Nonaka's avatar Seigo Nonaka
Browse files

Make Typeface.create thread safe.

Typeface.create has not been thread-safe. This CL makes it thread safe.
At the same time, reorganize cache and lock in Typeface.

Bug: 64606109
Test: bit FrameworksCoreTests:android.graphics.TypefaceTest
Test: bit CtsGraphicsTestCases:android.graphics.cts.TypefaceTest
Change-Id: Id18e9b8bfab508a26fc0c83af6b1ca97e132b732
parent b2fb1820
Loading
Loading
Loading
Loading
+87 −4
Original line number Diff line number Diff line
@@ -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[] {
@@ -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);
@@ -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++) {
@@ -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
            }
        }

    }

}
+68 −44
Original line number Diff line number Diff line
@@ -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,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;
@@ -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;
    }

@@ -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;