/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.core.graphics;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import android.annotation.SuppressLint;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.content.res.Resources;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Build;

import androidx.annotation.NonNull;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.core.content.res.FontResourcesParserCompat;
import androidx.core.content.res.FontResourcesParserCompat.FamilyResourceEntry;
import androidx.core.content.res.FontResourcesParserCompat.ProviderResourceEntry;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.provider.FontRequest;
import androidx.core.provider.FontsContractCompat;
import androidx.core.provider.MockFontProvider;
import androidx.core.test.R;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.testutils.WeightStyleFont;

import com.google.common.truth.Truth;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@SmallTest
@SuppressWarnings("deprecation")
public class TypefaceCompatTest {

    public Context mContext;
    public Resources mResources;

    @Before
    public void setUp() {
        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        mResources = mContext.getResources();
        MockFontProvider.prepareFontFiles(mContext);
    }

    @After
    public void tearDown() {
        MockFontProvider.cleanUpFontFiles(mContext);
    }

    // Signature to be used for authentication to access content provider.
    // In this test case, the content provider and consumer live in the same package, self package's
    // signature works.
    private static final List<List<byte[]>> SIGNATURE;
    static {
        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
        try {
            PackageManager manager = context.getPackageManager();
            PackageInfo info = manager.getPackageInfo(
                    context.getPackageName(), PackageManager.GET_SIGNATURES);
            ArrayList<byte[]> out = new ArrayList<>();
            for (Signature sig : info.signatures) {
                out.add(sig.toByteArray());
            }
            SIGNATURE = new ArrayList<>();
            SIGNATURE.add(out);
        } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Helper method to get the used font resource id by typeface.
     *
     * If the typeface is created from one of the R.font.large_a, R.font.large_b, R.font.large_c or
     * R.font.large_d resource, this method returns the resource id used by the typeface.
     */
    private static int getSelectedFontResourceId(Typeface typeface) {
        // The glyph for "a" in R.font.large_a font has a 3em width and glyph for "b", "c" and "d"
        // have 1em width. Similarly, The glyph for "b" in R.font.large_b font, the glyph for "c"
        // in R.font.large_c font, the glyph for "d" in R.font.large_d font has 3em width and the
        // glyph for the rest characters have 1em. Thus we can get the resource id of the source
        // font file by comparing width of "a", "b", "c" and "d".
        Paint p = new Paint();
        p.setTypeface(typeface);
        final int[] ids = { R.font.large_a, R.font.large_b, R.font.large_c, R.font.large_d };
        final float[] widths = {
            p.measureText("a"), p.measureText("b"), p.measureText("c"), p.measureText("d")
        };

        int maxIndex = Integer.MIN_VALUE;
        float maxValue = Float.MIN_VALUE;
        for (int i = 0; i < widths.length; ++i) {
            if (maxValue < widths[i]) {
                maxIndex = i;
                maxValue = widths[i];
            }
        }
        return ids[maxIndex];
    }

    /**
     * Helper method to obtain ProviderResourceEntry with overwriting correct signatures.
     */
    private ProviderResourceEntry getProviderResourceEntry(int id) {
        final ProviderResourceEntry entry;
        try {
            entry = (ProviderResourceEntry) FontResourcesParserCompat.parse(
                    mResources.getXml(id), mResources);
        } catch (XmlPullParserException | IOException e) {
            throw new RuntimeException(e);
        }
        final FontRequest parsedRequest = entry.getRequest();
        final FontRequest request = new FontRequest(parsedRequest.getProviderAuthority(),
                parsedRequest.getProviderPackage(), parsedRequest.getQuery(), SIGNATURE);
        return new ProviderResourceEntry(request, entry.getFetchStrategy(), entry.getTimeout(),
                entry.getSystemFontFamilyName());
    }

    public static class FontCallback extends ResourcesCompat.FontCallback {
        private final CountDownLatch mLatch;
        Typeface mTypeface;

        FontCallback(CountDownLatch latch) {
            mLatch = latch;
        }

        @Override
        public void onFontRetrieved(@NonNull Typeface typeface) {
            mTypeface = typeface;
            mLatch.countDown();
        }

        @Override
        public void onFontRetrievalFailed(int reason) {
            mLatch.countDown();
        }
    }

    @Test
    public void testCreateFromResourcesFamilyXml_resourceFont_NORMAL_asyncloading()
            throws Exception {
        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
        CountDownLatch latch = new CountDownLatch(1);
        final FontCallback callback = new FontCallback(latch);

        inst.runOnMainSync(new Runnable() {
            @Override
            public void run() {
                TypefaceCompat.createFromResourcesFamilyXml(mContext,
                        getProviderResourceEntry(R.font.styletest_async_providerfont), mResources,
                        R.font.styletest_async_providerfont,
                        "res/font/styletest_async_providerfont", 0,
                        Typeface.NORMAL, callback, null /* handler */, false /* isXmlRequest */);
            }
        });
        assertTrue(latch.await(5L, TimeUnit.SECONDS));
        assertEquals(R.font.large_a, getSelectedFontResourceId(callback.mTypeface));
    }

    @Test
    public void testCreateFromResourcesFamilyXml_resourceFont_ITALIC_asyncloading()
            throws Exception {
        CountDownLatch latch = new CountDownLatch(1);
        final FontCallback callback2 = new FontCallback(latch);
        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
        inst.runOnMainSync(new Runnable() {
            @Override
            public void run() {
                TypefaceCompat.createFromResourcesFamilyXml(mContext,
                        getProviderResourceEntry(R.font.styletest_async_providerfont), mResources,
                        R.font.styletest_async_providerfont,
                        "res/font/styletest_async_providerfont", 0, Typeface.ITALIC,
                        callback2, null /* handler */, false /* isXmlRequest */);
            }
        });
        assertTrue(latch.await(5L, TimeUnit.SECONDS));
        assertEquals(R.font.large_b, getSelectedFontResourceId(callback2.mTypeface));
    }

    @Test
    public void testCreateFromResourcesFamilyXml_resourceFont_BOLD_asyncloading()
            throws Exception {
        CountDownLatch latch = new CountDownLatch(1);
        final FontCallback callback3 = new FontCallback(latch);
        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
        inst.runOnMainSync(new Runnable() {
            @Override
            public void run() {
                TypefaceCompat.createFromResourcesFamilyXml(mContext,
                        getProviderResourceEntry(R.font.styletest_async_providerfont), mResources,
                        R.font.styletest_async_providerfont,
                        "res/font/styletest_async_providerfont", 0, Typeface.BOLD,
                        callback3, null /* handler */, false /* isXmlRequest */);
            }
        });
        assertTrue(latch.await(5L, TimeUnit.SECONDS));
        assertEquals(R.font.large_c, getSelectedFontResourceId(callback3.mTypeface));
    }

    @Test
    public void testCreateFromResourcesFamilyXml_resourceFont_BOLD_ITALIC_asyncloading()
            throws Exception {
        CountDownLatch latch = new CountDownLatch(1);
        final FontCallback callback4 = new FontCallback(latch);
        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
        inst.runOnMainSync(new Runnable() {
            @Override
            public void run() {
                TypefaceCompat.createFromResourcesFamilyXml(mContext,
                        getProviderResourceEntry(R.font.styletest_async_providerfont), mResources,
                        R.font.styletest_async_providerfont,
                        "res/font/styletest_async_providerfont", 0,
                        Typeface.BOLD_ITALIC, callback4, null /* handler */,
                        false /* isXmlRequest */);
            }
        });
        assertTrue(latch.await(5L, TimeUnit.SECONDS));
        assertEquals(R.font.large_d, getSelectedFontResourceId(callback4.mTypeface));
    }

    @Test
    public void testProviderFont_xmlRequest() {
        Typeface typeface = TypefaceCompat.createFromResourcesFamilyXml(mContext,
                getProviderResourceEntry(R.font.samplexmldownloadedfontblocking), mResources,
                R.font.samplexmldownloadedfontblocking,
                "res/font/samplexmldownloadedfontblocking", 0, Typeface.NORMAL, null,
                null /* handler */, true /* isXmlRequest */);

        assertNotNull(typeface);
        assertNotEquals(Typeface.DEFAULT, typeface);
    }

    @Test
    public void testProviderFont_nonXmlRequest_noCallback() {
        // If we don't give a callback, the request should be blocking.
        Typeface typeface = TypefaceCompat.createFromResourcesFamilyXml(mContext,
                getProviderResourceEntry(R.font.samplexmldownloadedfontblocking), mResources,
                R.font.samplexmldownloadedfontblocking,
                "res/font/samplexmldownloadedfontblocking", 0, Typeface.NORMAL, null,
                null /* handler */, false /* isXmlRequest */);

        assertNotNull(typeface);
        assertNotEquals(Typeface.DEFAULT, typeface);
    }

    @Test
    public void testProviderFont_nonXmlRequest_withCallback() throws InterruptedException {
        Instrumentation inst = InstrumentationRegistry.getInstrumentation();
        CountDownLatch latch = new CountDownLatch(1);
        final FontCallback callback = new FontCallback(latch);
        FontsContractCompat.resetTypefaceCache();

        final Typeface[] result = new Typeface[1];
        inst.runOnMainSync(new Runnable() {
            @Override
            public void run() {
                result[0] = TypefaceCompat.createFromResourcesFamilyXml(mContext,
                        getProviderResourceEntry(R.font.samplexmldownloadedfontblocking),
                        mResources, R.font.samplexmldownloadedfontblocking,
                        "res/font/samplexmldownloadedfontblocking", 0, Typeface.NORMAL,
                        callback, null /* handler */, false /* isXmlRequest */);
            }
        });
        assertTrue(latch.await(5L, TimeUnit.SECONDS));
        assertNotNull(callback.mTypeface);
        assertNull(result[0]);
    }

    @Test
    public void testProviderFont_nonXmlRequest_withCallback_cached() throws InterruptedException {
        Instrumentation inst = InstrumentationRegistry.getInstrumentation();
        CountDownLatch latch = new CountDownLatch(1);
        final FontCallback callback = new FontCallback(latch);
        FontsContractCompat.resetTypefaceCache();

        final Typeface[] result = new Typeface[2];
        inst.runOnMainSync(new Runnable() {
            @Override
            public void run() {
                result[0] = TypefaceCompat.createFromResourcesFamilyXml(mContext,
                        getProviderResourceEntry(R.font.samplexmldownloadedfontblocking),
                        mResources, R.font.samplexmldownloadedfontblocking,
                        "res/font/samplexmldownloadedfontblocking", 0, Typeface.NORMAL,
                        callback, null /* handler */, false /* isXmlRequest */);
            }
        });
        assertTrue(latch.await(5L, TimeUnit.SECONDS));
        assertNotNull(callback.mTypeface);
        assertNull(result[0]);

        latch = new CountDownLatch(1);
        final FontCallback callback2 = new FontCallback(latch);

        inst.runOnMainSync(new Runnable() {
            @Override
            public void run() {
                result[1] = TypefaceCompat.createFromResourcesFamilyXml(mContext,
                        getProviderResourceEntry(R.font.samplexmldownloadedfontblocking),
                        mResources, R.font.samplexmldownloadedfontblocking,
                        "res/font/samplexmldownloadedfontblocking", 0, Typeface.NORMAL,
                        callback2, null /* handler */, false /* isXmlRequest */);
            }
        });
        assertTrue(latch.await(5L, TimeUnit.SECONDS));
        assertNotNull(callback2.mTypeface);
        assertNotNull(result[1]);
    }

    @Test
    public void testCreateFromResourcesFamilyXml_resourceFont() throws Exception {
        @SuppressLint("ResourceType")
        // We are retrieving the XML font as an XML resource for testing purposes.
        final FamilyResourceEntry entry = FontResourcesParserCompat.parse(
                mResources.getXml(R.font.styletestfont), mResources);
        Typeface typeface = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry, mResources,
                R.font.styletestfont, "res/font/styletestfont", 0, Typeface.NORMAL, null /* callback
                 */,
                null /* handler */,
                false /* isXmlRequest */);
        Typeface cachedTypeface = TypefaceCompat.findFromCache(
                mResources, R.font.styletestfont, "res/font/styletestfont", 0, Typeface.NORMAL);
        assertNotNull(cachedTypeface);
        assertEquals(typeface, cachedTypeface);
        typeface = Typeface.create(typeface, Typeface.NORMAL);
        // styletestfont has a node of fontStyle="normal" fontWeight="400" font="@font/large_a".
        assertEquals(R.font.large_a, getSelectedFontResourceId(typeface));

        typeface = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry, mResources,
                R.font.styletestfont, "res/font/styletestfont", 0, Typeface.ITALIC,
                null /* callback */, null /* handler */, false /* isXmlRequest */);
        cachedTypeface = TypefaceCompat.findFromCache(
                mResources, R.font.styletestfont, "res/font/styletestfont", 0, Typeface.ITALIC);
        assertNotNull(cachedTypeface);
        assertEquals(typeface, cachedTypeface);
        typeface = Typeface.create(typeface, Typeface.ITALIC);
        // styletestfont has a node of fontStyle="italic" fontWeight="400" font="@font/large_b".
        assertEquals(R.font.large_b, getSelectedFontResourceId(typeface));

        typeface = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry, mResources,
                R.font.styletestfont, "res/font/styletestfont", 0, Typeface.BOLD,
                null /* callback */, null /* handler */, false /* isXmlRequest */);
        cachedTypeface = TypefaceCompat.findFromCache(
                mResources, R.font.styletestfont, "res/font/styletestfont", 0, Typeface.BOLD);
        assertNotNull(cachedTypeface);
        assertEquals(typeface, cachedTypeface);
        typeface = Typeface.create(typeface, Typeface.BOLD);
        // styletestfont has a node of fontStyle="normal" fontWeight="700" font="@font/large_c".
        assertEquals(R.font.large_c, getSelectedFontResourceId(typeface));

        typeface = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry, mResources,
                R.font.styletestfont, "res/font/styletestfont", 0, Typeface.BOLD_ITALIC,
                null /* callback */, null /* handler */, false /* isXmlRequest */);
        cachedTypeface = TypefaceCompat.findFromCache(mResources, R.font.styletestfont,
                "res/font/styletestfont", 0, Typeface.BOLD_ITALIC);
        assertNotNull(cachedTypeface);
        assertEquals(typeface, cachedTypeface);
        typeface = Typeface.create(typeface, Typeface.BOLD_ITALIC);
        // styletestfont has a node of fontStyle="italic" fontWeight="700" font="@font/large_d".
        assertEquals(R.font.large_d, getSelectedFontResourceId(typeface));
    }

    private Typeface getLargerTypeface(String text, Typeface typeface1, Typeface typeface2) {
        Paint p1 = new Paint();
        p1.setTypeface(typeface1);
        float width1 = p1.measureText(text);
        Paint p2 = new Paint();
        p2.setTypeface(typeface2);
        float width2 = p2.measureText(text);

        if (width1 > width2) {
            return typeface1;
        } else if (width1 < width2) {
            return typeface2;
        } else {
            assertTrue(false);
            return null;
        }
    }

    @Test
    public void testCreateFromResourcesFamilyXml_resourceTtcFont() throws Exception {
        // Here we test that building typefaces by indexing in font collections works correctly.
        // We want to ensure that the built typefaces correspond to the fonts with the right index.
        // sample_font_collection.ttc contains two fonts (with indices 0 and 1). The first one has
        // glyph "a" of 3em width, and all the other glyphs 1em. The second one has glyph "b" of
        // 3em width, and all the other glyphs 1em. Hence, we can compare the width of these
        // glyphs to assert that ttc indexing works.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            // Creating typefaces with ttc index was only supported in the API starting with N.
            return;
        }
        final FamilyResourceEntry entry1 = FontResourcesParserCompat.parse(
                mResources.getXml(R.font.ttctestfont1), mResources);
        Typeface typeface1 = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry1,
                mResources, R.font.ttctestfont1, "res/font/ttctestfont1", 0,
                Typeface.NORMAL, null /* callback */, null /*handler */, false /* isXmlRequest */);
        assertNotNull(typeface1);
        final FamilyResourceEntry entry2 = FontResourcesParserCompat.parse(
                mResources.getXml(R.font.ttctestfont2), mResources);
        Typeface typeface2 = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry2,
                mResources, R.font.ttctestfont2, "res/font/ttctestfont2", 0, Typeface.NORMAL,
                null /* callback */, null /*handler */, false /* isXmlRequest */);
        assertNotNull(typeface2);

        assertEquals(getLargerTypeface("a", typeface1, typeface2), typeface1);
        assertEquals(getLargerTypeface("b", typeface1, typeface2), typeface2);
    }

    @Test
    public void testCreateFromResourcesFamilyXml_resourceFontWithVariationSettings()
            throws Exception {
        // Here we test that specifying variation settings for fonts in XMLs works correctly.
        // We build typefaces from two families containing one font each, using the same font
        // resource, but having different values for the 'wdth' tag. Then we measure the painted
        // text to ensure that the tag affects the text width. The font resource used supports
        // the 'wdth' axis for the dash (-) character.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            // Variation settings are only supported on O and newer.
            return;
        }
        final FamilyResourceEntry entry1 = FontResourcesParserCompat.parse(
                mResources.getXml(R.font.variationsettingstestfont1), mResources);
        Typeface typeface1 = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry1,
                mResources, R.font.variationsettingstestfont1,
                "res/font/variationsettingstestfont1", 0, Typeface.NORMAL, null /* callback */,
                null /*handler */, false /* isXmlRequest */);
        assertNotNull(typeface1);
        final FamilyResourceEntry entry2 = FontResourcesParserCompat.parse(
                mResources.getXml(R.font.variationsettingstestfont2), mResources);
        Typeface typeface2 = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry2,
                mResources, R.font.variationsettingstestfont2,
                "res/font/variationsettingstestfont2", 0, Typeface.NORMAL, null /* callback */,
                null /*handler */, false /* isXmlRequest */);
        assertNotNull(typeface2);

        assertEquals(getLargerTypeface("-", typeface1, typeface2), typeface2);
    }

    @Test
    public void testCreateFromResourcesFontFile() {
        Typeface typeface = TypefaceCompat.createFromResourcesFontFile(mContext, mResources,
                R.font.large_a, "res/font/large_a.ttf", 0, Typeface.NORMAL);
        assertNotNull(typeface);
        Typeface cachedTypeface = TypefaceCompat.findFromCache(
                mResources, R.font.large_a, "res/font/large_a.ttf", 0, Typeface.NORMAL);
        assertNotNull(cachedTypeface);
        assertEquals(typeface, cachedTypeface);
        assertEquals(R.font.large_a, getSelectedFontResourceId(typeface));

        typeface = TypefaceCompat.createFromResourcesFontFile(mContext, mResources, R.font.large_b,
                "res/font/large_b.ttf", 0, Typeface.NORMAL);
        assertNotNull(typeface);
        cachedTypeface = TypefaceCompat.findFromCache(
                mResources, R.font.large_b, "res/font/large_b.ttf", 0, Typeface.NORMAL);
        assertNotNull(cachedTypeface);
        assertEquals(typeface, cachedTypeface);
        assertEquals(R.font.large_b, getSelectedFontResourceId(typeface));
    }

    @Test
    public void testApplyFontFromFamilyUsingStyle() {
        // Test we get the correct font from the family when specifying a style instead of
        // applying the style to the "normal" font. The fonts used have a specific property.
        // The first font has glyph "a" of 3em width, and all the other glyphs 1em
        // and the second one has glyph "c" of 3em width, and all the other glyphs 1em.
        final Typeface family = ResourcesCompat.getFont(mContext, R.font.styletestfont);
        assertNotNull(family);

        final AppCompatTextView appCompatTextView = new AppCompatTextView(
                new ContextThemeWrapper(mContext, R.style.Theme_AppCompat_Light));
        assertNotNull(appCompatTextView);

        appCompatTextView.setTypeface(family, Typeface.NORMAL);
        final Typeface large_a = appCompatTextView.getTypeface();
        assertEquals(R.font.large_a, getSelectedFontResourceId(large_a));

        appCompatTextView.setTypeface(family, Typeface.BOLD);
        final Typeface large_c = appCompatTextView.getTypeface();
        assertEquals(R.font.large_c, getSelectedFontResourceId(large_c));

        appCompatTextView.setTypeface(family, Typeface.NORMAL);
        final Typeface large_a2 = appCompatTextView.getTypeface();
        assertEquals(R.font.large_a, getSelectedFontResourceId(large_a2));
    }

    @Test
    public void testTypeFaceCompatCreate() {
        final Typeface family = ResourcesCompat.getFont(mContext, R.font.styletestfont);
        assertNotNull(family);

        final Typeface large_a = TypefaceCompat.create(mContext, family, Typeface.NORMAL);
        assertEquals(R.font.large_a, getSelectedFontResourceId(large_a));

        final Typeface large_c = TypefaceCompat.create(mContext, family, Typeface.BOLD);
        assertEquals(R.font.large_c, getSelectedFontResourceId(large_c));

        final Typeface large_a2 = TypefaceCompat.create(mContext, family, Typeface.NORMAL);
        assertEquals(R.font.large_a, getSelectedFontResourceId(large_a2));

        final Typeface defaultTypeface = TypefaceCompat.create(mContext, null, Typeface.NORMAL);
        assertNotNull(defaultTypeface);
    }

    @Test
    @SdkSuppress(minSdkVersion = 18) // API 14-20 backport fails on 17
    public void testTypeFaceCompatCreateWithExactStyle_upright() {
        doTypefaceCreate(false);
    }

    private void doTypefaceCreate(boolean italic) {
        final Typeface family = ResourcesCompat.getFont(mContext, R.font.weighttestfont);
        assertNotNull(family);

        final int[] weights = new int[]{100, 400, 900};
        final float[] widths = new float[weights.length];

        Paint p = new Paint();
        p.setTextSize(120);

        // Normal font style
        for (int i = 0, size = weights.length; i < size; i++) {
            final int weight = weights[i];
            final Typeface t = TypefaceCompat.create(mContext, family, weight, false);
            char wideChar = new WeightStyleFont().getWideCharacter(weight, italic);
            p.setTypeface(t);
            widths[i] = p.measureText("" + wideChar);
        }

        float expectedWeight = italic ? 120.0f : 360.0f;
        Truth.assertThat(widths).usingTolerance(0.1)
                .containsExactly(expectedWeight, expectedWeight, expectedWeight);
        // check the test validity by matching a never-matching char
        Truth.assertThat(p.measureText("" + WeightStyleFont.SkinnyChar)).isNotWithin(0.1f)
                .of(expectedWeight);
    }

    @Test
    @SdkSuppress(maxSdkVersion = 16) // API 14-20 backport fails on 17
    public void testTypeFaceCompatCreateWithExactStyle_upright_api14_to_16() {
        doTypefaceCreate(false);
    }


    @Test
    @SdkSuppress(minSdkVersion = 18) // API 14-20 backport is too flakey for CI
    public void testTypeFaceCompatCreateWithExactStyle_italic() {
        doTypefaceCreate(true);
    }

    @Test
    @SdkSuppress(maxSdkVersion = 16) // API 14-20 backport is too flakey for CI
    public void testTypeFaceCompatCreateWithExactStyle_italic_api14_to_16() {
        doTypefaceCreate(true);
    }

    @Test
    public void testTypeFaceCompatCreateWithExactStyle_fallbackFamily() {
        // Fallback family
        final Typeface defaultTypeface = TypefaceCompat.create(mContext, null, 400, false);
        assertNotNull(defaultTypeface);
    }

    @Test
    public void testTypeFaceCompatCreateWithExactStyle_preconditions() {
        // Preconditions
        try {
            TypefaceCompat.create(null, null, 400, false);
            fail("Expected IllegalArgumentException");
        } catch (IllegalArgumentException expected) {
            Truth.assertThat(expected).hasMessageThat()
                    .isEqualTo("Context cannot be null");
        }

        try {
            TypefaceCompat.create(mContext, null, -1, false);
            fail("Expected IllegalArgumentException");
        } catch (IllegalArgumentException expected) {
            Truth.assertThat(expected).hasMessageThat()
                    .isEqualTo("weight is out of range of [1, 1000] (too low)");
        }

        try {
            TypefaceCompat.create(mContext, null, 1001, false);
            fail("Expected IllegalArgumentException");
        } catch (IllegalArgumentException expected) {
            Truth.assertThat(expected).hasMessageThat()
                    .isEqualTo("weight is out of range of [1, 1000] (too high)");
        }
    }
}
