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 */
16package android.support.v4.provider;
17
18import static android.support.v4.provider.FontsContractCompat.Columns.RESULT_CODE_FONT_NOT_FOUND;
19import static android.support.v4.provider.FontsContractCompat.Columns.RESULT_CODE_FONT_UNAVAILABLE;
20import static android.support.v4.provider.FontsContractCompat.Columns.RESULT_CODE_MALFORMED_QUERY;
21import static android.support.v4.provider.FontsContractCompat.Columns.RESULT_CODE_OK;
22
23import static org.junit.Assert.assertEquals;
24import static org.junit.Assert.assertNotNull;
25import static org.junit.Assert.assertNotSame;
26import static org.junit.Assert.assertNull;
27import static org.junit.Assert.assertTrue;
28import static org.junit.Assert.fail;
29import static org.mockito.Matchers.anyInt;
30import static org.mockito.Matchers.anyString;
31import static org.mockito.Mockito.mock;
32import static org.mockito.Mockito.when;
33
34import android.app.Instrumentation;
35import android.content.Context;
36import android.content.pm.ApplicationInfo;
37import android.content.pm.PackageInfo;
38import android.content.pm.PackageManager;
39import android.content.pm.PackageManager.NameNotFoundException;
40import android.content.pm.ProviderInfo;
41import android.content.pm.Signature;
42import android.graphics.Typeface;
43import android.support.test.InstrumentationRegistry;
44import android.support.test.filters.SmallTest;
45import android.support.test.runner.AndroidJUnit4;
46import android.support.v4.provider.FontsContractCompat.FontFamilyResult;
47import android.support.v4.provider.FontsContractCompat.FontInfo;
48import android.util.Base64;
49
50import org.junit.After;
51import org.junit.Before;
52import org.junit.Test;
53import org.junit.runner.RunWith;
54
55import java.util.ArrayList;
56import java.util.Arrays;
57import java.util.Collections;
58import java.util.List;
59
60/**
61 * Unit tests for {@link FontsContractCompat}.
62 */
63@RunWith(AndroidJUnit4.class)
64@SmallTest
65public class FontsContractCompatTest {
66    private static final String AUTHORITY = "android.support.provider.fonts.font";
67    private static final String PACKAGE = "android.support.compat.test";
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    private static final byte[] BYTE_ARRAY =
91            Base64.decode("e04fd020ea3a6910a2d808002b30", Base64.DEFAULT);
92    // Use a different instance to test byte array comparison
93    private static final byte[] BYTE_ARRAY_COPY =
94            Base64.decode("e04fd020ea3a6910a2d808002b30", Base64.DEFAULT);
95    private static final byte[] BYTE_ARRAY_2 =
96            Base64.decode("e04fd020ea3a6910a2d808002b32", Base64.DEFAULT);
97    private Instrumentation mInstrumentation;
98    private Context mContext;
99
100    @Before
101    public void setUp() throws Exception {
102        mInstrumentation = InstrumentationRegistry.getInstrumentation();
103        mContext = mInstrumentation.getTargetContext();
104        MockFontProvider.prepareFontFiles(
105                InstrumentationRegistry.getInstrumentation().getTargetContext());
106    }
107
108    @After
109    public void tearDown() {
110        MockFontProvider.cleanUpFontFiles(
111                InstrumentationRegistry.getInstrumentation().getTargetContext());
112    }
113
114    private static class TestCallback extends FontsContractCompat.FontRequestCallback {
115        private Typeface mTypeface;
116
117        private int mSuccessCallCount;
118        private int mFailedCallCount;
119
120        public void onTypefaceRetrieved(Typeface typeface) {
121            mTypeface = typeface;
122            mSuccessCallCount++;
123        }
124
125        public void onTypefaceRequestFailed(int reason) {
126            mFailedCallCount++;
127        }
128
129        public Typeface getTypeface() {
130            return mTypeface;
131        }
132
133        public int getSuccessCallCount() {
134            return mSuccessCallCount;
135        }
136
137        public int getFailedCallCount() {
138            return mFailedCallCount;
139        }
140    }
141
142    @Test
143    public void typefaceNotCacheTest() throws NameNotFoundException {
144        FontRequest request = new FontRequest(
145                AUTHORITY, PACKAGE, MockFontProvider.SINGLE_FONT_FAMILY_QUERY, SIGNATURE);
146        FontFamilyResult result = FontsContractCompat.fetchFonts(
147                mContext, null /* cancellation signal */, request);
148        assertEquals(FontFamilyResult.STATUS_OK, result.getStatusCode());
149        Typeface typeface = FontsContractCompat.buildTypeface(
150                mContext, null /* cancellation signal */, result.getFonts());
151
152        FontFamilyResult result2 = FontsContractCompat.fetchFonts(
153                mContext, null /* cancellation signal */, request);
154        assertEquals(FontFamilyResult.STATUS_OK, result2.getStatusCode());
155        Typeface typeface2 = FontsContractCompat.buildTypeface(
156                mContext, null /* cancellation signal */, result2.getFonts());
157
158        // Neither fetchFonts nor buildTypeface should cache the Typeface.
159        assertNotSame(typeface, typeface2);
160    }
161
162    @Test
163    public void testGetFontFromProvider_resultOK() {
164        FontRequest request = new FontRequest(
165                AUTHORITY, PACKAGE, MockFontProvider.SINGLE_FONT_FAMILY2_QUERY, SIGNATURE);
166        FontInfo[] fonts = FontsContractCompat.getFontFromProvider(
167                mContext, request, AUTHORITY, null);
168        assertNotNull(fonts);
169        assertEquals(1, fonts.length);
170        FontInfo font = fonts[0];
171        assertEquals(0, font.getTtcIndex());
172        assertEquals(700, font.getWeight());
173        assertTrue(font.isItalic());
174        assertNotNull(font.getUri());
175        assertEquals(RESULT_CODE_OK, font.getResultCode());
176    }
177
178    @Test
179    public void testGetFontFromProvider_providerDoesntReturnAllFields() {
180        FontRequest request = new FontRequest(
181                AUTHORITY, PACKAGE, MockFontProvider.MANDATORY_FIELDS_ONLY_QUERY, SIGNATURE);
182        FontInfo[] fonts = FontsContractCompat.getFontFromProvider(
183                mContext, request, AUTHORITY, null);
184        assertNotNull(fonts);
185        assertEquals(1, fonts.length);
186        FontInfo font = fonts[0];
187        assertEquals(0, font.getTtcIndex());
188        assertEquals(RESULT_CODE_OK, font.getResultCode());
189    }
190
191    @Test
192    public void testGetFontFromProvider_resultFontNotFound() {
193        FontRequest request = new FontRequest(
194                AUTHORITY, PACKAGE, MockFontProvider.NOT_FOUND_QUERY, SIGNATURE);
195        FontInfo[] fonts = FontsContractCompat.getFontFromProvider(
196                mContext, request, AUTHORITY, null);
197        assertNotNull(fonts);
198        assertEquals(1, fonts.length);
199        FontInfo font = fonts[0];
200        assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode());
201    }
202
203    @Test
204    public void testGetFontFromProvider_resultFontUnavailable() {
205        FontRequest request = new FontRequest(
206                AUTHORITY, PACKAGE, MockFontProvider.UNAVAILABLE_QUERY, SIGNATURE);
207        FontInfo[] fonts = FontsContractCompat.getFontFromProvider(
208                mContext, request, AUTHORITY, null);
209
210        assertNotNull(fonts);
211        assertEquals(1, fonts.length);
212        FontInfo font = fonts[0];
213        assertEquals(RESULT_CODE_FONT_UNAVAILABLE, font.getResultCode());
214    }
215
216    @Test
217    public void testGetFontFromProvider_resultMalformedQuery() {
218        FontRequest request = new FontRequest(
219                AUTHORITY, PACKAGE, MockFontProvider.MALFORMED_QUERY, SIGNATURE);
220        FontInfo[] fonts = FontsContractCompat.getFontFromProvider(
221                mContext, request, AUTHORITY, null);
222
223        assertNotNull(fonts);
224        assertEquals(1, fonts.length);
225        FontInfo font = fonts[0];
226        assertEquals(RESULT_CODE_MALFORMED_QUERY, font.getResultCode());
227    }
228
229    @Test
230    public void testGetFontFromProvider_resultFontNotFoundSecondRow() {
231        FontRequest request = new FontRequest(
232                AUTHORITY, PACKAGE, MockFontProvider.NOT_FOUND_SECOND_QUERY, SIGNATURE);
233        FontInfo[] fonts = FontsContractCompat.getFontFromProvider(
234                mContext, request, AUTHORITY, null);
235
236        assertNotNull(fonts);
237        assertEquals(2, fonts.length);
238
239        FontInfo font = fonts[0];
240        assertEquals(RESULT_CODE_OK, font.getResultCode());
241
242        font = fonts[1];
243        assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode());
244    }
245
246    @Test
247    public void testGetFontFromProvider_resultFontNotFoundOtherRow() {
248        FontRequest request = new FontRequest(
249                AUTHORITY, PACKAGE, MockFontProvider.NOT_FOUND_THIRD_QUERY, SIGNATURE);
250        FontInfo[] fonts = FontsContractCompat.getFontFromProvider(
251                mContext, request, AUTHORITY, null);
252
253        assertNotNull(fonts);
254        assertEquals(3, fonts.length);
255
256        FontInfo font = fonts[0];
257        assertEquals(RESULT_CODE_OK, font.getResultCode());
258
259        font = fonts[1];
260        assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode());
261
262        font = fonts[2];
263        assertEquals(RESULT_CODE_OK, font.getResultCode());
264    }
265
266    public void testGetFontFromProvider_resultCodeIsNegativeNumber() {
267        FontRequest request = new FontRequest(
268                AUTHORITY, PACKAGE, MockFontProvider.NEGATIVE_ERROR_CODE_QUERY, SIGNATURE);
269        FontInfo[] fonts = FontsContractCompat.getFontFromProvider(
270                mContext, request, AUTHORITY, null);
271
272
273        assertNotNull(fonts);
274        assertEquals(1, fonts.length);
275        FontInfo font = fonts[0];
276        assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode());
277    }
278
279    @Test
280    public void testGetProvider_providerNotFound() {
281        PackageManager packageManager = mock(PackageManager.class);
282        when(packageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(null);
283
284        FontRequest request = new FontRequest(AUTHORITY, PACKAGE, "query", SIGNATURE);
285        try {
286            FontsContractCompat.getProvider(packageManager, request, null);
287            fail();
288        } catch (NameNotFoundException e) {
289            // pass
290        }
291    }
292
293    @Test
294    public void testGetProvider_noCerts()
295            throws PackageManager.NameNotFoundException {
296        PackageManager packageManager = mContext.getPackageManager();
297
298        List<List<byte[]>> emptyList = Collections.emptyList();
299
300        FontRequest request = new FontRequest(AUTHORITY, PACKAGE, "query", emptyList);
301        assertNull(FontsContractCompat.getProvider(packageManager, request, null));
302    }
303
304    @Test
305    public void testGetProvider_wrongCerts()
306            throws PackageManager.NameNotFoundException {
307        PackageManager packageManager = mock(PackageManager.class);
308        setupPackageManager(packageManager);
309
310        byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT);
311        List<byte[]> certList = Arrays.asList(wrongCert);
312        FontRequest requestWrongCerts = new FontRequest(
313                AUTHORITY, PACKAGE, "query", Arrays.asList(certList));
314
315        assertNull(FontsContractCompat.getProvider(packageManager, requestWrongCerts, null));
316    }
317
318    @Test
319    public void testGetProvider_correctCerts()
320            throws PackageManager.NameNotFoundException {
321        PackageManager packageManager = mock(PackageManager.class);
322        ProviderInfo info = setupPackageManager(packageManager);
323
324        List<byte[]> certList = Arrays.asList(BYTE_ARRAY);
325        FontRequest requestRightCerts = new FontRequest(
326                AUTHORITY, PACKAGE, "query", Arrays.asList(certList));
327        ProviderInfo result =
328                FontsContractCompat.getProvider(packageManager, requestRightCerts, null);
329
330        assertEquals(info, result);
331    }
332
333    @Test
334    public void testGetProvider_moreCerts()
335            throws PackageManager.NameNotFoundException {
336        PackageManager packageManager = mock(PackageManager.class);
337        setupPackageManager(packageManager);
338
339        byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT);
340        List<byte[]> certList = Arrays.asList(wrongCert, BYTE_ARRAY);
341        FontRequest requestRightCerts = new FontRequest(
342                AUTHORITY, PACKAGE, "query", Arrays.asList(certList));
343        assertNull(FontsContractCompat.getProvider(packageManager, requestRightCerts, null));
344    }
345
346    @Test
347    public void testGetProvider_duplicateCerts()
348            throws PackageManager.NameNotFoundException {
349        PackageManager packageManager = mock(PackageManager.class);
350        setupPackageManager(packageManager);
351        PackageInfo packageInfo = new PackageInfo();
352        Signature signature = mock(Signature.class);
353        when(signature.toByteArray()).thenReturn(BYTE_ARRAY_COPY);
354        Signature signature2 = mock(Signature.class);
355        when(signature2.toByteArray()).thenReturn(BYTE_ARRAY_COPY);
356        packageInfo.packageName = PACKAGE;
357        packageInfo.signatures = new Signature[] { signature, signature2 };
358        when(packageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo);
359
360        // The provider has {BYTE_ARRAY_COPY, BYTE_ARRAY_COPY}, the request has
361        // {BYTE_ARRAY_2, BYTE_ARRAY_COPY}.
362        List<byte[]> certList = Arrays.asList(BYTE_ARRAY_2, BYTE_ARRAY_COPY);
363        FontRequest requestRightCerts = new FontRequest(
364                AUTHORITY, PACKAGE, "query", Arrays.asList(certList));
365        assertNull(FontsContractCompat.getProvider(packageManager, requestRightCerts, null));
366    }
367
368    @Test
369    public void testGetProvider_correctCertsSeveralSets()
370            throws PackageManager.NameNotFoundException {
371        PackageManager packageManager = mock(PackageManager.class);
372        ProviderInfo info = setupPackageManager(packageManager);
373
374        List<List<byte[]>> certList = new ArrayList<>();
375        byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT);
376        certList.add(Arrays.asList(wrongCert));
377        certList.add(Arrays.asList(BYTE_ARRAY));
378        FontRequest requestRightCerts = new FontRequest(AUTHORITY, PACKAGE, "query", certList);
379        ProviderInfo result =
380                FontsContractCompat.getProvider(packageManager, requestRightCerts, null);
381
382        assertEquals(info, result);
383    }
384
385    @Test
386    public void testGetProvider_wrongPackage()
387            throws PackageManager.NameNotFoundException {
388        PackageManager packageManager = mContext.getPackageManager();
389
390        List<List<byte[]>> certList = new ArrayList<>();
391        certList.add(Arrays.asList(BYTE_ARRAY));
392        FontRequest requestRightCerts = new FontRequest(
393                AUTHORITY, "com.wrong.package.name", "query", certList);
394        try {
395            FontsContractCompat.getProvider(packageManager, requestRightCerts, null);
396            fail();
397        } catch (NameNotFoundException e) {
398            // pass
399        }
400    }
401
402    private ProviderInfo setupPackageManager(PackageManager packageManager)
403            throws PackageManager.NameNotFoundException {
404        ProviderInfo info = new ProviderInfo();
405        info.packageName = PACKAGE;
406        info.applicationInfo = new ApplicationInfo();
407        when(packageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info);
408        PackageInfo packageInfo = new PackageInfo();
409        Signature signature = mock(Signature.class);
410        when(signature.toByteArray()).thenReturn(BYTE_ARRAY_COPY);
411        packageInfo.packageName = PACKAGE;
412        packageInfo.signatures = new Signature[] { signature };
413        when(packageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo);
414        return info;
415    }
416}
417