1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.graphics;
18
19import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertNotNull;
21
22import android.annotation.SuppressLint;
23import android.app.Instrumentation;
24import android.content.Context;
25import android.content.pm.PackageInfo;
26import android.content.pm.PackageManager;
27import android.content.pm.Signature;
28import android.content.res.Resources;
29import android.graphics.Paint;
30import android.graphics.Typeface;
31import android.support.compat.test.R;
32import android.support.test.InstrumentationRegistry;
33import android.support.test.filters.SmallTest;
34import android.support.testutils.PollingCheck;
35import android.support.v4.content.res.FontResourcesParserCompat;
36import android.support.v4.content.res.FontResourcesParserCompat.FamilyResourceEntry;
37import android.support.v4.content.res.FontResourcesParserCompat.ProviderResourceEntry;
38import android.support.v4.provider.FontRequest;
39import android.support.v4.provider.MockFontProvider;
40import android.widget.TextView;
41
42import org.junit.After;
43import org.junit.Before;
44import org.junit.Test;
45import org.xmlpull.v1.XmlPullParserException;
46
47import java.io.IOException;
48import java.util.ArrayList;
49import java.util.List;
50
51@SmallTest
52public class TypefaceCompatTest {
53
54    public Context mContext;
55    public Resources mResources;
56
57    @Before
58    public void setUp() {
59        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
60        mResources = mContext.getResources();
61        MockFontProvider.prepareFontFiles(mContext);
62    }
63
64    @After
65    public void tearDown() {
66        MockFontProvider.cleanUpFontFiles(mContext);
67    }
68
69    // Signature to be used for authentication to access content provider.
70    // In this test case, the content provider and consumer live in the same package, self package's
71    // signature works.
72    private static final List<List<byte[]>> SIGNATURE;
73    static {
74        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
75        try {
76            PackageManager manager = context.getPackageManager();
77            PackageInfo info = manager.getPackageInfo(
78                    context.getPackageName(), PackageManager.GET_SIGNATURES);
79            ArrayList<byte[]> out = new ArrayList<>();
80            for (Signature sig : info.signatures) {
81                out.add(sig.toByteArray());
82            }
83            SIGNATURE = new ArrayList<>();
84            SIGNATURE.add(out);
85        } catch (PackageManager.NameNotFoundException e) {
86            throw new RuntimeException(e);
87        }
88    }
89
90    /**
91     * Helper method to get the used font resource id by typeface.
92     *
93     * If the typeface is created from one of the R.font.large_a, R.font.large_b, R.font.large_c or
94     * R.font.large_d resource, this method returns the resource id used by the typeface.
95     */
96    private static int getSelectedFontResourceId(Typeface typeface) {
97        // The glyph for "a" in R.font.large_a font has a 3em width and glyph for "b", "c" and "d"
98        // have 1em width. Similarly, The glyph for "b" in R.font.large_b font, the glyph for "c"
99        // in R.font.large_c font, the glyph for "d" in R.font.large_d font has 3em width and the
100        // glyph for the rest characters have 1em. Thus we can get the resource id of the source
101        // font file by comparing width of "a", "b", "c" and "d".
102        Paint p = new Paint();
103        p.setTypeface(typeface);
104        final int[] ids = { R.font.large_a, R.font.large_b, R.font.large_c, R.font.large_d };
105        final float[] widths = {
106            p.measureText("a"), p.measureText("b"), p.measureText("c"), p.measureText("d")
107        };
108
109        int maxIndex = Integer.MIN_VALUE;
110        float maxValue = Float.MIN_VALUE;
111        for (int i = 0; i < widths.length; ++i) {
112            if (maxValue < widths[i]) {
113                maxIndex = i;
114                maxValue = widths[i];
115            }
116        }
117        return ids[maxIndex];
118    }
119
120    /**
121     * Helper method to obtain ProviderResourceEntry with overwriting correct signatures.
122     */
123    private ProviderResourceEntry getProviderResourceEntry(int id) {
124        final ProviderResourceEntry entry;
125        try {
126            entry = (ProviderResourceEntry) FontResourcesParserCompat.parse(
127                    mResources.getXml(id), mResources);
128        } catch (XmlPullParserException | IOException e) {
129            throw new RuntimeException(e);
130        }
131        final FontRequest parsedRequest = entry.getRequest();
132        final FontRequest request = new FontRequest(parsedRequest.getProviderAuthority(),
133                parsedRequest.getProviderPackage(), parsedRequest.getQuery(), SIGNATURE);
134        return new ProviderResourceEntry(request, entry.getFetchStrategy(), entry.getTimeout());
135    }
136
137    @Test
138    public void testCreateFromResourcesFamilyXml_resourceFont_syncloading() throws Exception {
139        Typeface typeface = TypefaceCompat.createFromResourcesFamilyXml(mContext,
140                getProviderResourceEntry(R.font.styletest_sync_providerfont), mResources,
141                R.font.styletest_sync_providerfont, Typeface.NORMAL, null /* TextView */);
142        typeface = Typeface.create(typeface, Typeface.NORMAL);
143        assertEquals(R.font.large_a, getSelectedFontResourceId(typeface));
144
145        typeface = TypefaceCompat.createFromResourcesFamilyXml(mContext,
146                getProviderResourceEntry(R.font.styletest_sync_providerfont), mResources,
147                R.font.styletest_sync_providerfont, Typeface.ITALIC, null /* TextView */);
148        typeface = Typeface.create(typeface, Typeface.ITALIC);
149        assertEquals(R.font.large_b, getSelectedFontResourceId(typeface));
150
151        typeface = TypefaceCompat.createFromResourcesFamilyXml(mContext,
152                getProviderResourceEntry(R.font.styletest_sync_providerfont), mResources,
153                R.font.styletest_sync_providerfont, Typeface.BOLD, null /* TextView */);
154        typeface = Typeface.create(typeface, Typeface.BOLD);
155        assertEquals(R.font.large_c, getSelectedFontResourceId(typeface));
156
157        typeface = TypefaceCompat.createFromResourcesFamilyXml(mContext,
158                getProviderResourceEntry(R.font.styletest_sync_providerfont), mResources,
159                R.font.styletest_sync_providerfont, Typeface.BOLD_ITALIC, null /* TextView */);
160        typeface = Typeface.create(typeface, Typeface.BOLD_ITALIC);
161        assertEquals(R.font.large_d, getSelectedFontResourceId(typeface));
162    }
163
164    @Test
165    public void testCreateFromResourcesFamilyXml_resourceFont_asyncloading() throws Exception {
166        Instrumentation inst = InstrumentationRegistry.getInstrumentation();
167        final TextView textView = new TextView(mContext);
168        PollingCheck.PollingCheckCondition condition = new PollingCheck.PollingCheckCondition() {
169            @Override
170            public boolean canProceed() {
171                return textView.getTypeface() != null;
172            }
173        };
174
175        textView.setTypeface(null);
176        inst.runOnMainSync(new Runnable() {
177            @Override
178            public void run() {
179                TypefaceCompat.createFromResourcesFamilyXml(mContext,
180                        getProviderResourceEntry(R.font.styletest_async_providerfont), mResources,
181                        R.font.styletest_async_providerfont, Typeface.NORMAL, textView);
182            }
183        });
184        PollingCheck.waitFor(condition);
185        assertEquals(R.font.large_a, getSelectedFontResourceId(textView.getTypeface()));
186
187        textView.setTypeface(null);
188        inst.runOnMainSync(new Runnable() {
189            @Override
190            public void run() {
191                TypefaceCompat.createFromResourcesFamilyXml(mContext,
192                        getProviderResourceEntry(R.font.styletest_async_providerfont), mResources,
193                        R.font.styletest_async_providerfont, Typeface.ITALIC, textView);
194            }
195        });
196        PollingCheck.waitFor(condition);
197        assertEquals(R.font.large_b, getSelectedFontResourceId(textView.getTypeface()));
198
199        textView.setTypeface(null);
200        inst.runOnMainSync(new Runnable() {
201            @Override
202            public void run() {
203                TypefaceCompat.createFromResourcesFamilyXml(mContext,
204                        getProviderResourceEntry(R.font.styletest_async_providerfont), mResources,
205                        R.font.styletest_async_providerfont, Typeface.BOLD, textView);
206            }
207        });
208        PollingCheck.waitFor(condition);
209        assertEquals(R.font.large_c, getSelectedFontResourceId(textView.getTypeface()));
210
211        textView.setTypeface(null);
212        inst.runOnMainSync(new Runnable() {
213            @Override
214            public void run() {
215                TypefaceCompat.createFromResourcesFamilyXml(mContext,
216                        getProviderResourceEntry(R.font.styletest_async_providerfont), mResources,
217                        R.font.styletest_async_providerfont, Typeface.BOLD_ITALIC, textView);
218            }
219        });
220        PollingCheck.waitFor(condition);
221        assertEquals(R.font.large_d, getSelectedFontResourceId(textView.getTypeface()));
222    }
223
224    @Test
225    public void testCreateFromResourcesFamilyXml_resourceFont() throws Exception {
226        @SuppressLint("ResourceType")
227        // We are retrieving the XML font as an XML resource for testing purposes.
228        final FamilyResourceEntry entry = FontResourcesParserCompat.parse(
229                mResources.getXml(R.font.styletestfont), mResources);
230        Typeface typeface = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry, mResources,
231                R.font.styletestfont, Typeface.NORMAL, null /* text view */);
232        assertEquals(typeface, TypefaceCompat.findFromCache(
233                mResources, R.font.styletestfont, Typeface.NORMAL));
234        typeface = Typeface.create(typeface, Typeface.NORMAL);
235        // styletestfont has a node of fontStyle="normal" fontWeight="400" font="@font/large_a".
236        assertEquals(R.font.large_a, getSelectedFontResourceId(typeface));
237
238        typeface = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry, mResources,
239                R.font.styletestfont, Typeface.ITALIC, null);
240        assertEquals(typeface, TypefaceCompat.findFromCache(
241                mResources, R.font.styletestfont, Typeface.ITALIC));
242        typeface = Typeface.create(typeface, Typeface.ITALIC);
243        // styletestfont has a node of fontStyle="italic" fontWeight="400" font="@font/large_b".
244        assertEquals(R.font.large_b, getSelectedFontResourceId(typeface));
245
246        typeface = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry, mResources,
247                R.font.styletestfont, Typeface.BOLD, null);
248        assertEquals(typeface, TypefaceCompat.findFromCache(
249                mResources, R.font.styletestfont, Typeface.BOLD));
250        typeface = Typeface.create(typeface, Typeface.BOLD);
251        // styletestfont has a node of fontStyle="normal" fontWeight="700" font="@font/large_c".
252        assertEquals(R.font.large_c, getSelectedFontResourceId(typeface));
253
254        typeface = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry, mResources,
255                R.font.styletestfont, Typeface.BOLD_ITALIC, null);
256        assertEquals(typeface, TypefaceCompat.findFromCache(
257                mResources, R.font.styletestfont, Typeface.BOLD_ITALIC));
258        typeface = Typeface.create(typeface, Typeface.BOLD_ITALIC);
259        // styletestfont has a node of fontStyle="italic" fontWeight="700" font="@font/large_d".
260        assertEquals(R.font.large_d, getSelectedFontResourceId(typeface));
261    }
262
263    @Test
264    public void testCreateFromResourcesFontFile() {
265        Typeface typeface = TypefaceCompat.createFromResourcesFontFile(mContext, mResources,
266                R.font.large_a, "res/font/large_a.ttf", Typeface.NORMAL);
267        assertNotNull(typeface);
268        assertEquals(typeface, TypefaceCompat.findFromCache(
269                mResources, R.font.large_a, Typeface.NORMAL));
270        assertEquals(R.font.large_a, getSelectedFontResourceId(typeface));
271
272        typeface = TypefaceCompat.createFromResourcesFontFile(mContext, mResources, R.font.large_b,
273                "res/font/large_b.ttf", Typeface.NORMAL);
274        assertNotNull(typeface);
275        assertEquals(typeface, TypefaceCompat.findFromCache(
276                mResources, R.font.large_b, Typeface.NORMAL));
277        assertEquals(R.font.large_b, getSelectedFontResourceId(typeface));
278    }
279}
280