| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.content.browser.font; |
| |
| import static junit.framework.Assert.assertEquals; |
| |
| import static org.mockito.AdditionalMatchers.aryEq; |
| import static org.mockito.ArgumentMatchers.argThat; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.ArgumentMatchers.isNull; |
| import static org.mockito.ArgumentMatchers.notNull; |
| import static org.mockito.Mockito.timeout; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| import static org.mockito.MockitoAnnotations.initMocks; |
| |
| import android.content.Context; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.res.AssetFileDescriptor; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.ParcelFileDescriptor; |
| import android.test.IsolatedContext; |
| import android.test.mock.MockContentProvider; |
| import android.test.mock.MockContentResolver; |
| import android.test.mock.MockContext; |
| |
| import androidx.core.provider.FontRequest; |
| import androidx.core.provider.FontsContractCompat.Columns; |
| import androidx.core.provider.FontsContractCompat.FontFamilyResult; |
| import androidx.core.provider.FontsContractCompat.FontInfo; |
| import androidx.test.filters.SmallTest; |
| |
| import com.google.common.collect.ImmutableMap; |
| |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.stubbing.OngoingStubbing; |
| |
| import org.chromium.base.metrics.RecordHistogram; |
| import org.chromium.base.test.BaseJUnit4ClassRunner; |
| import org.chromium.base.test.util.CriteriaHelper; |
| import org.chromium.blink.mojom.AndroidFontLookup; |
| import org.chromium.blink.mojom.AndroidFontLookup.GetUniqueNameLookupTableResponse; |
| import org.chromium.blink.mojom.AndroidFontLookup.MatchLocalFontByUniqueNameResponse; |
| import org.chromium.content.browser.font.AndroidFontLookupImpl.FetchFontName; |
| import org.chromium.content.browser.font.AndroidFontLookupImpl.FetchFontResult; |
| import org.chromium.content_public.browser.test.NativeLibraryTestUtils; |
| import org.chromium.mojo.MojoTestRule; |
| |
| import java.util.Map; |
| |
| /** |
| * Tests the {@link AndroidFontLookup} implementation. |
| */ |
| @RunWith(BaseJUnit4ClassRunner.class) |
| public final class AndroidFontLookupImplTest { |
| private static final String FULL_FONT_NAME_1 = "foo"; |
| private static final String FONT_QUERY_1 = "name=Foo&weight=400"; |
| private static final String FULL_FONT_NAME_2 = "bar"; |
| private static final String FONT_QUERY_2 = "name=Bar&weight=400"; |
| private static final String FULL_FONT_NAME_3 = "bar bold"; |
| private static final String FONT_QUERY_3 = "name=Bar&weight=700"; |
| private static final String AUTHORITY = "com.google.android.gms.fonts"; |
| private static final Uri URI = Uri.parse("content://com.google.android.gms.fonts/123"); |
| private static final Uri URI2 = Uri.parse("content://com.google.android.gms.fonts/456"); |
| private static final int FD = 42; |
| private static final int FD2 = 43; |
| private static final long RUN_LOOP_TIMEOUT_MS = 50; |
| |
| @Rule |
| public MojoTestRule mMojoTestRule = new MojoTestRule(MojoTestRule.MojoCore.INITIALIZE); |
| |
| @Mock |
| private FontsContractWrapper mMockFontsContractWrapper; |
| @Mock |
| private ParcelFileDescriptor mMockFileDescriptor; |
| @Mock |
| private ParcelFileDescriptor mMockFileDescriptor2; |
| private Context mMockContext; |
| |
| @Mock |
| private GetUniqueNameLookupTableResponse mGetUniqueNameLookupTableCallback; |
| @Mock |
| private MatchLocalFontByUniqueNameResponse mMatchLocalFontByUniqueNameCallback; |
| |
| private AndroidFontLookupImpl mAndroidFontLookup; |
| |
| @Before |
| public void setUp() { |
| initMocks(this); |
| |
| NativeLibraryTestUtils.loadNativeLibraryNoBrowserProcess(); |
| |
| MockContentResolver resolver = new MockContentResolver(); |
| MockContext mockContext = new MockContext(); |
| when(mMockFileDescriptor.detachFd()).thenReturn(FD); |
| when(mMockFileDescriptor2.detachFd()).thenReturn(FD2); |
| resolver.addProvider(AUTHORITY, new MockContentProvider(mockContext) { |
| @Override |
| public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) { |
| if (url.equals(URI)) { |
| return new AssetFileDescriptor(mMockFileDescriptor, 0, -1); |
| } else if (url.equals(URI2)) { |
| return new AssetFileDescriptor(mMockFileDescriptor2, 0, -1); |
| } else { |
| return null; |
| } |
| } |
| }); |
| mMockContext = new IsolatedContext(resolver, mockContext); |
| |
| Map<String, String> fullFontNameToQuery = ImmutableMap.of(FULL_FONT_NAME_1, FONT_QUERY_1, |
| FULL_FONT_NAME_2, FONT_QUERY_2, FULL_FONT_NAME_3, FONT_QUERY_3); |
| |
| mAndroidFontLookup = new AndroidFontLookupImpl( |
| mMockContext, mMockFontsContractWrapper, fullFontNameToQuery); |
| } |
| |
| @SmallTest |
| @Test |
| public void testGetUniqueNameLookupTable_Available() { |
| String[] expected = new String[] {FULL_FONT_NAME_2, FULL_FONT_NAME_3, FULL_FONT_NAME_1}; |
| |
| mAndroidFontLookup.getUniqueNameLookupTable(mGetUniqueNameLookupTableCallback); |
| |
| mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); |
| verify(mGetUniqueNameLookupTableCallback).call(aryEq(expected)); |
| } |
| |
| @SmallTest |
| @Test |
| public void testGetUniqueNameLookupTable_MultipleFonts() throws NameNotFoundException { |
| // All 3 fonts should be found in results. |
| mAndroidFontLookup.getUniqueNameLookupTable(mGetUniqueNameLookupTableCallback); |
| mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); |
| verify(mGetUniqueNameLookupTableCallback) |
| .call(aryEq(new String[] {FULL_FONT_NAME_2, FULL_FONT_NAME_3, FULL_FONT_NAME_1})); |
| |
| // Bar Bold is not available. |
| FontFamilyResult result3 = |
| new FontFamilyResult(FontFamilyResult.STATUS_OK, new FontInfo[0]); |
| whenFetchFontsWith(FONT_QUERY_3).thenReturn(result3); |
| mAndroidFontLookup.matchLocalFontByUniqueName( |
| FULL_FONT_NAME_3, mMatchLocalFontByUniqueNameCallback); |
| mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); |
| verify(mMatchLocalFontByUniqueNameCallback, |
| timeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)) |
| .call(isNull()); |
| |
| // Bar Bold should now be excluded from list. |
| mAndroidFontLookup.getUniqueNameLookupTable(mGetUniqueNameLookupTableCallback); |
| mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); |
| verify(mGetUniqueNameLookupTableCallback) |
| .call(aryEq(new String[] {FULL_FONT_NAME_2, FULL_FONT_NAME_1})); |
| } |
| |
| @SmallTest |
| @Test |
| public void testMatchLocalFontByUniqueName_UnsupportedFontName() { |
| mAndroidFontLookup.matchLocalFontByUniqueName("baz", mMatchLocalFontByUniqueNameCallback); |
| |
| mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); |
| verify(mMatchLocalFontByUniqueNameCallback, |
| timeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)) |
| .call(isNull()); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_NAME_HISTOGRAM, FetchFontName.OTHER)); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_RESULT_HISTOGRAM, |
| FetchFontResult.FAILED_UNEXPECTED_NAME)); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramTotalCountForTesting( |
| AndroidFontLookupImpl.MATCH_LOCAL_FONT_BY_UNIQUE_NAME_HISTOGRAM)); |
| } |
| |
| @SmallTest |
| @Test |
| public void testMatchLocalFontByUniqueName_BadResultStatus() throws NameNotFoundException { |
| FontFamilyResult result = |
| new FontFamilyResult(FontFamilyResult.STATUS_UNEXPECTED_DATA_PROVIDED, null); |
| whenFetchFontsWith(FONT_QUERY_1).thenReturn(result); |
| |
| mAndroidFontLookup.matchLocalFontByUniqueName( |
| FULL_FONT_NAME_1, mMatchLocalFontByUniqueNameCallback); |
| |
| mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); |
| verify(mMatchLocalFontByUniqueNameCallback, |
| timeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)) |
| .call(isNull()); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_NAME_HISTOGRAM, FetchFontName.OTHER)); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_RESULT_HISTOGRAM, |
| FetchFontResult.FAILED_STATUS_CODE)); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramTotalCountForTesting( |
| AndroidFontLookupImpl.MATCH_LOCAL_FONT_BY_UNIQUE_NAME_HISTOGRAM)); |
| } |
| |
| @SmallTest |
| @Test |
| public void testMatchLocalFontByUniqueName_EmptyResults() throws NameNotFoundException { |
| FontFamilyResult result = new FontFamilyResult(FontFamilyResult.STATUS_OK, new FontInfo[0]); |
| whenFetchFontsWith(FONT_QUERY_1).thenReturn(result); |
| |
| mAndroidFontLookup.matchLocalFontByUniqueName( |
| FULL_FONT_NAME_1, mMatchLocalFontByUniqueNameCallback); |
| |
| mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); |
| verify(mMatchLocalFontByUniqueNameCallback, |
| timeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)) |
| .call(isNull()); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_NAME_HISTOGRAM, FetchFontName.OTHER)); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_RESULT_HISTOGRAM, |
| FetchFontResult.FAILED_NON_UNIQUE_RESULT)); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramTotalCountForTesting( |
| AndroidFontLookupImpl.MATCH_LOCAL_FONT_BY_UNIQUE_NAME_HISTOGRAM)); |
| } |
| |
| @SmallTest |
| @Test |
| public void testMatchLocalFontByUniqueName_BadFontInfoStatus() throws NameNotFoundException { |
| FontInfo fontInfo = new FontInfo(URI, 0, 400, false, Columns.RESULT_CODE_FONT_NOT_FOUND); |
| FontFamilyResult result = |
| new FontFamilyResult(FontFamilyResult.STATUS_OK, new FontInfo[] {fontInfo}); |
| whenFetchFontsWith(FONT_QUERY_1).thenReturn(result); |
| |
| mAndroidFontLookup.matchLocalFontByUniqueName( |
| FULL_FONT_NAME_1, mMatchLocalFontByUniqueNameCallback); |
| |
| mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); |
| verify(mMatchLocalFontByUniqueNameCallback, |
| timeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)) |
| .call(isNull()); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_NAME_HISTOGRAM, FetchFontName.OTHER)); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_RESULT_HISTOGRAM, |
| FetchFontResult.FAILED_RESULT_CODE)); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramTotalCountForTesting( |
| AndroidFontLookupImpl.MATCH_LOCAL_FONT_BY_UNIQUE_NAME_HISTOGRAM)); |
| } |
| |
| @SmallTest |
| @Test |
| public void testMatchLocalFontByUniqueName_Throws() throws NameNotFoundException { |
| whenFetchFontsWith(FONT_QUERY_1).thenThrow(new NameNotFoundException()); |
| |
| mAndroidFontLookup.matchLocalFontByUniqueName( |
| FULL_FONT_NAME_1, mMatchLocalFontByUniqueNameCallback); |
| |
| mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); |
| verify(mMatchLocalFontByUniqueNameCallback, |
| timeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)) |
| .call(isNull()); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_NAME_HISTOGRAM, FetchFontName.OTHER)); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_RESULT_HISTOGRAM, |
| FetchFontResult.FAILED_EXCEPTION)); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramTotalCountForTesting( |
| AndroidFontLookupImpl.MATCH_LOCAL_FONT_BY_UNIQUE_NAME_HISTOGRAM)); |
| } |
| |
| @SmallTest |
| @Test |
| public void testMatchLocalFontByUniqueName_NoRetry() throws NameNotFoundException { |
| // Request font and fail. |
| whenFetchFontsWith(FONT_QUERY_1).thenThrow(new NameNotFoundException()); |
| |
| mAndroidFontLookup.matchLocalFontByUniqueName( |
| FULL_FONT_NAME_1, mMatchLocalFontByUniqueNameCallback); |
| |
| mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); |
| verify(mMatchLocalFontByUniqueNameCallback, |
| timeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)) |
| .call(isNull()); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_RESULT_HISTOGRAM, |
| FetchFontResult.FAILED_EXCEPTION)); |
| |
| // Second request should early out with FAILED_AVOID_RETRY. |
| mAndroidFontLookup.matchLocalFontByUniqueName( |
| FULL_FONT_NAME_1, mMatchLocalFontByUniqueNameCallback); |
| |
| mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); |
| verify(mMatchLocalFontByUniqueNameCallback, |
| timeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL).times(2)) |
| .call(isNull()); |
| |
| assertEquals(2, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_NAME_HISTOGRAM, FetchFontName.OTHER)); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_RESULT_HISTOGRAM, |
| FetchFontResult.FAILED_AVOID_RETRY)); |
| |
| assertEquals(2, |
| RecordHistogram.getHistogramTotalCountForTesting( |
| AndroidFontLookupImpl.MATCH_LOCAL_FONT_BY_UNIQUE_NAME_HISTOGRAM)); |
| } |
| |
| @SmallTest |
| @Test |
| public void testMatchLocalFontByUniqueName_Success() throws NameNotFoundException { |
| FontInfo fontInfo = new FontInfo(URI, 0, 400, false, Columns.RESULT_CODE_OK); |
| FontFamilyResult result = |
| new FontFamilyResult(FontFamilyResult.STATUS_OK, new FontInfo[] {fontInfo}); |
| whenFetchFontsWith(FONT_QUERY_1).thenReturn(result); |
| |
| mAndroidFontLookup.matchLocalFontByUniqueName( |
| FULL_FONT_NAME_1, mMatchLocalFontByUniqueNameCallback); |
| |
| mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); |
| verify(mMatchLocalFontByUniqueNameCallback, |
| timeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)) |
| .call(notNull()); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_NAME_HISTOGRAM, FetchFontName.OTHER)); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramValueCountForTesting( |
| AndroidFontLookupImpl.FETCH_FONT_RESULT_HISTOGRAM, |
| FetchFontResult.SUCCESS)); |
| |
| assertEquals(1, |
| RecordHistogram.getHistogramTotalCountForTesting( |
| AndroidFontLookupImpl.MATCH_LOCAL_FONT_BY_UNIQUE_NAME_HISTOGRAM)); |
| } |
| |
| private OngoingStubbing<FontFamilyResult> whenFetchFontsWith(String query) |
| throws NameNotFoundException { |
| return when(mMockFontsContractWrapper.fetchFonts(eq(mMockContext), isNull(), |
| argThat((FontRequest r) -> r.getQuery().equals(query)))); |
| } |
| } |