| /* |
| * 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.provider; |
| |
| import static androidx.core.provider.FontsContractCompat.Columns.RESULT_CODE_FONT_NOT_FOUND; |
| import static androidx.core.provider.FontsContractCompat.Columns.RESULT_CODE_FONT_UNAVAILABLE; |
| import static androidx.core.provider.FontsContractCompat.Columns.RESULT_CODE_MALFORMED_QUERY; |
| import static androidx.core.provider.FontsContractCompat.Columns.RESULT_CODE_OK; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNotSame; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.anyString; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.when; |
| |
| import android.app.Instrumentation; |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.pm.ProviderInfo; |
| import android.content.pm.Signature; |
| import android.graphics.Typeface; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.util.Base64; |
| |
| import androidx.annotation.NonNull; |
| import androidx.core.provider.FontsContractCompat.FontFamilyResult; |
| import androidx.core.provider.FontsContractCompat.FontInfo; |
| import androidx.test.ext.junit.runners.AndroidJUnit4; |
| import androidx.test.filters.MediumTest; |
| import androidx.test.platform.app.InstrumentationRegistry; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Unit tests for {@link FontsContractCompat}. |
| */ |
| @RunWith(AndroidJUnit4.class) |
| @MediumTest |
| @SuppressWarnings("deprecation") |
| public class FontsContractCompatTest { |
| private static final String AUTHORITY = "androidx.core.provider.fonts.font"; |
| private static final String PACKAGE = "androidx.core.test"; |
| |
| // 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); |
| } |
| } |
| |
| private static final byte[] BYTE_ARRAY = |
| Base64.decode("e04fd020ea3a6910a2d808002b30", Base64.DEFAULT); |
| // Use a different instance to test byte array comparison |
| private static final byte[] BYTE_ARRAY_COPY = |
| Base64.decode("e04fd020ea3a6910a2d808002b30", Base64.DEFAULT); |
| private static final byte[] BYTE_ARRAY_2 = |
| Base64.decode("e04fd020ea3a6910a2d808002b32", Base64.DEFAULT); |
| private Instrumentation mInstrumentation; |
| private Context mContext; |
| |
| @Before |
| public void setUp() throws Exception { |
| mInstrumentation = InstrumentationRegistry.getInstrumentation(); |
| mContext = mInstrumentation.getTargetContext(); |
| MockFontProvider.prepareFontFiles( |
| InstrumentationRegistry.getInstrumentation().getTargetContext()); |
| } |
| |
| @After |
| public void tearDown() { |
| MockFontProvider.cleanUpFontFiles( |
| InstrumentationRegistry.getInstrumentation().getTargetContext()); |
| } |
| |
| @Test |
| public void typefaceNotCacheTest() throws NameNotFoundException { |
| FontRequest request = new FontRequest( |
| AUTHORITY, PACKAGE, MockFontProvider.SINGLE_FONT_FAMILY_QUERY, SIGNATURE); |
| FontFamilyResult result = FontsContractCompat.fetchFonts( |
| mContext, null /* cancellation signal */, request); |
| assertEquals(FontFamilyResult.STATUS_OK, result.getStatusCode()); |
| Typeface typeface = FontsContractCompat.buildTypeface( |
| mContext, null /* cancellation signal */, result.getFonts()); |
| |
| FontFamilyResult result2 = FontsContractCompat.fetchFonts( |
| mContext, null /* cancellation signal */, request); |
| assertEquals(FontFamilyResult.STATUS_OK, result2.getStatusCode()); |
| Typeface typeface2 = FontsContractCompat.buildTypeface( |
| mContext, null /* cancellation signal */, result2.getFonts()); |
| |
| // Neither fetchFonts nor buildTypeface should cache the Typeface. |
| assertNotSame(typeface, typeface2); |
| } |
| |
| @Test |
| public void testGetFontFromProvider_resultOK() { |
| FontRequest request = new FontRequest( |
| AUTHORITY, PACKAGE, MockFontProvider.SINGLE_FONT_FAMILY2_QUERY, SIGNATURE); |
| FontInfo[] fonts = FontProvider.query( |
| mContext, request, AUTHORITY, null); |
| assertNotNull(fonts); |
| assertEquals(1, fonts.length); |
| FontInfo font = fonts[0]; |
| assertEquals(0, font.getTtcIndex()); |
| assertEquals(700, font.getWeight()); |
| assertTrue(font.isItalic()); |
| assertNotNull(font.getUri()); |
| assertEquals(RESULT_CODE_OK, font.getResultCode()); |
| } |
| |
| @Test |
| public void testGetFontFromProvider_providerDoesntReturnAllFields() { |
| FontRequest request = new FontRequest( |
| AUTHORITY, PACKAGE, MockFontProvider.MANDATORY_FIELDS_ONLY_QUERY, SIGNATURE); |
| FontInfo[] fonts = FontProvider.query( |
| mContext, request, AUTHORITY, null); |
| assertNotNull(fonts); |
| assertEquals(1, fonts.length); |
| FontInfo font = fonts[0]; |
| assertEquals(0, font.getTtcIndex()); |
| assertEquals(RESULT_CODE_OK, font.getResultCode()); |
| } |
| |
| @Test |
| public void testGetFontFromProvider_resultFontNotFound() { |
| FontRequest request = new FontRequest( |
| AUTHORITY, PACKAGE, MockFontProvider.NOT_FOUND_QUERY, SIGNATURE); |
| FontInfo[] fonts = FontProvider.query( |
| mContext, request, AUTHORITY, null); |
| assertNotNull(fonts); |
| assertEquals(1, fonts.length); |
| FontInfo font = fonts[0]; |
| assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode()); |
| } |
| |
| @Test |
| public void testGetFontFromProvider_resultFontUnavailable() { |
| FontRequest request = new FontRequest( |
| AUTHORITY, PACKAGE, MockFontProvider.UNAVAILABLE_QUERY, SIGNATURE); |
| FontInfo[] fonts = FontProvider.query( |
| mContext, request, AUTHORITY, null); |
| |
| assertNotNull(fonts); |
| assertEquals(1, fonts.length); |
| FontInfo font = fonts[0]; |
| assertEquals(RESULT_CODE_FONT_UNAVAILABLE, font.getResultCode()); |
| } |
| |
| @Test |
| public void testGetFontFromProvider_resultMalformedQuery() { |
| FontRequest request = new FontRequest( |
| AUTHORITY, PACKAGE, MockFontProvider.MALFORMED_QUERY, SIGNATURE); |
| FontInfo[] fonts = FontProvider.query( |
| mContext, request, AUTHORITY, null); |
| |
| assertNotNull(fonts); |
| assertEquals(1, fonts.length); |
| FontInfo font = fonts[0]; |
| assertEquals(RESULT_CODE_MALFORMED_QUERY, font.getResultCode()); |
| } |
| |
| @Test |
| public void testGetFontFromProvider_resultFontNotFoundSecondRow() { |
| FontRequest request = new FontRequest( |
| AUTHORITY, PACKAGE, MockFontProvider.NOT_FOUND_SECOND_QUERY, SIGNATURE); |
| FontInfo[] fonts = FontProvider.query( |
| mContext, request, AUTHORITY, null); |
| |
| assertNotNull(fonts); |
| assertEquals(2, fonts.length); |
| |
| FontInfo font = fonts[0]; |
| assertEquals(RESULT_CODE_OK, font.getResultCode()); |
| |
| font = fonts[1]; |
| assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode()); |
| } |
| |
| @Test |
| public void testGetFontFromProvider_resultFontNotFoundOtherRow() { |
| FontRequest request = new FontRequest( |
| AUTHORITY, PACKAGE, MockFontProvider.NOT_FOUND_THIRD_QUERY, SIGNATURE); |
| FontInfo[] fonts = FontProvider.query( |
| mContext, request, AUTHORITY, null); |
| |
| assertNotNull(fonts); |
| assertEquals(3, fonts.length); |
| |
| FontInfo font = fonts[0]; |
| assertEquals(RESULT_CODE_OK, font.getResultCode()); |
| |
| font = fonts[1]; |
| assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode()); |
| |
| font = fonts[2]; |
| assertEquals(RESULT_CODE_OK, font.getResultCode()); |
| } |
| |
| public void testGetFontFromProvider_resultCodeIsNegativeNumber() { |
| FontRequest request = new FontRequest( |
| AUTHORITY, PACKAGE, MockFontProvider.NEGATIVE_ERROR_CODE_QUERY, SIGNATURE); |
| FontInfo[] fonts = FontProvider.query( |
| mContext, request, AUTHORITY, null); |
| |
| |
| assertNotNull(fonts); |
| assertEquals(1, fonts.length); |
| FontInfo font = fonts[0]; |
| assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode()); |
| } |
| |
| @Test |
| public void testGetProvider_providerNotFound() { |
| PackageManager packageManager = mock(PackageManager.class); |
| when(packageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(null); |
| |
| FontRequest request = new FontRequest(AUTHORITY, PACKAGE, "query", SIGNATURE); |
| try { |
| FontProvider.getProvider(packageManager, request, null); |
| fail(); |
| } catch (NameNotFoundException e) { |
| // pass |
| } |
| } |
| |
| @Test |
| public void testGetProvider_noCerts() |
| throws PackageManager.NameNotFoundException { |
| PackageManager packageManager = mContext.getPackageManager(); |
| |
| List<List<byte[]>> emptyList = Collections.emptyList(); |
| |
| FontRequest request = new FontRequest(AUTHORITY, PACKAGE, "query", emptyList); |
| assertNull(FontProvider.getProvider(packageManager, request, null)); |
| } |
| |
| @Test |
| public void testGetProvider_wrongCerts() |
| throws PackageManager.NameNotFoundException { |
| PackageManager packageManager = mock(PackageManager.class); |
| setupPackageManager(packageManager); |
| |
| byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT); |
| List<byte[]> certList = Arrays.asList(wrongCert); |
| FontRequest requestWrongCerts = new FontRequest( |
| AUTHORITY, PACKAGE, "query", Arrays.asList(certList)); |
| |
| assertNull(FontProvider.getProvider(packageManager, requestWrongCerts, null)); |
| } |
| |
| @Test |
| public void testGetProvider_correctCerts() |
| throws PackageManager.NameNotFoundException { |
| PackageManager packageManager = mock(PackageManager.class); |
| ProviderInfo info = setupPackageManager(packageManager); |
| |
| List<byte[]> certList = Arrays.asList(BYTE_ARRAY); |
| FontRequest requestRightCerts = new FontRequest( |
| AUTHORITY, PACKAGE, "query", Arrays.asList(certList)); |
| ProviderInfo result = |
| FontProvider.getProvider(packageManager, requestRightCerts, null); |
| |
| assertEquals(info, result); |
| } |
| |
| @Test |
| public void testGetProvider_moreCerts() |
| throws PackageManager.NameNotFoundException { |
| PackageManager packageManager = mock(PackageManager.class); |
| setupPackageManager(packageManager); |
| |
| byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT); |
| List<byte[]> certList = Arrays.asList(wrongCert, BYTE_ARRAY); |
| FontRequest requestRightCerts = new FontRequest( |
| AUTHORITY, PACKAGE, "query", Arrays.asList(certList)); |
| assertNull(FontProvider.getProvider(packageManager, requestRightCerts, null)); |
| } |
| |
| @Test |
| @SuppressWarnings("deprecation") |
| public void testGetProvider_duplicateCerts() |
| throws PackageManager.NameNotFoundException { |
| PackageManager packageManager = mock(PackageManager.class); |
| setupPackageManager(packageManager); |
| PackageInfo packageInfo = new PackageInfo(); |
| Signature signature = mock(Signature.class); |
| when(signature.toByteArray()).thenReturn(BYTE_ARRAY_COPY); |
| Signature signature2 = mock(Signature.class); |
| when(signature2.toByteArray()).thenReturn(BYTE_ARRAY_COPY); |
| packageInfo.packageName = PACKAGE; |
| packageInfo.signatures = new Signature[] { signature, signature2 }; |
| when(packageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo); |
| |
| // The provider has {BYTE_ARRAY_COPY, BYTE_ARRAY_COPY}, the request has |
| // {BYTE_ARRAY_2, BYTE_ARRAY_COPY}. |
| List<byte[]> certList = Arrays.asList(BYTE_ARRAY_2, BYTE_ARRAY_COPY); |
| FontRequest requestRightCerts = new FontRequest( |
| AUTHORITY, PACKAGE, "query", Arrays.asList(certList)); |
| assertNull(FontProvider.getProvider(packageManager, requestRightCerts, null)); |
| } |
| |
| @Test |
| public void testGetProvider_correctCertsSeveralSets() |
| throws PackageManager.NameNotFoundException { |
| PackageManager packageManager = mock(PackageManager.class); |
| ProviderInfo info = setupPackageManager(packageManager); |
| |
| List<List<byte[]>> certList = new ArrayList<>(); |
| byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT); |
| certList.add(Arrays.asList(wrongCert)); |
| certList.add(Arrays.asList(BYTE_ARRAY)); |
| FontRequest requestRightCerts = new FontRequest(AUTHORITY, PACKAGE, "query", certList); |
| ProviderInfo result = |
| FontProvider.getProvider(packageManager, requestRightCerts, null); |
| |
| assertEquals(info, result); |
| } |
| |
| @Test |
| public void testGetProvider_wrongPackage() |
| throws PackageManager.NameNotFoundException { |
| PackageManager packageManager = mContext.getPackageManager(); |
| |
| List<List<byte[]>> certList = new ArrayList<>(); |
| certList.add(Arrays.asList(BYTE_ARRAY)); |
| FontRequest requestRightCerts = new FontRequest( |
| AUTHORITY, "com.wrong.package.name", "query", certList); |
| try { |
| FontProvider.getProvider(packageManager, requestRightCerts, null); |
| fail(); |
| } catch (NameNotFoundException e) { |
| // pass |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| private ProviderInfo setupPackageManager(PackageManager packageManager) |
| throws PackageManager.NameNotFoundException { |
| ProviderInfo info = new ProviderInfo(); |
| info.packageName = PACKAGE; |
| info.applicationInfo = new ApplicationInfo(); |
| when(packageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info); |
| PackageInfo packageInfo = new PackageInfo(); |
| Signature signature = mock(Signature.class); |
| when(signature.toByteArray()).thenReturn(BYTE_ARRAY_COPY); |
| packageInfo.packageName = PACKAGE; |
| packageInfo.signatures = new Signature[] { signature }; |
| when(packageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo); |
| return info; |
| } |
| |
| @Test |
| public void testGetFontSync_invalidUri() throws InterruptedException { |
| final Instrumentation inst = InstrumentationRegistry.getInstrumentation(); |
| final FontRequest request = new FontRequest( |
| AUTHORITY, PACKAGE, MockFontProvider.INVALID_URI, SIGNATURE); |
| final CountDownLatch latch = new CountDownLatch(1); |
| final FontCallback callback = new FontCallback(latch); |
| |
| inst.runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| Handler handler = new Handler(Looper.getMainLooper()); |
| FontsContractCompat.requestFont(mContext, request, Typeface.NORMAL, |
| false /* isBlockingFetch */, 300 /* timeout */, handler, callback); |
| } |
| }); |
| assertTrue(latch.await(5L, TimeUnit.SECONDS)); |
| assertNull(callback.mTypeface); |
| } |
| |
| public static class FontCallback extends FontsContractCompat.FontRequestCallback { |
| private final CountDownLatch mLatch; |
| Typeface mTypeface; |
| |
| FontCallback(CountDownLatch latch) { |
| mLatch = latch; |
| } |
| |
| @Override |
| public void onTypefaceRetrieved(@NonNull Typeface typeface) { |
| mTypeface = typeface; |
| mLatch.countDown(); |
| } |
| |
| @Override |
| public void onTypefaceRequestFailed(int reason) { |
| mLatch.countDown(); |
| } |
| } |
| } |