chromium/chrome/android/junit/src/org/chromium/chrome/browser/fonts/FontPreloaderUnitTest.java

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.fonts;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.graphics.Typeface;
import android.os.Handler;
import android.os.SystemClock;

import androidx.core.content.res.ResourcesCompat;
import androidx.core.content.res.ResourcesCompat.FontCallback;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.LooperMode.Mode;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadows.ShadowSystemClock;

import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.fonts.FontPreloaderUnitTest.ShadowResourcesCompat;

import java.util.ArrayList;
import java.util.List;

/** Unit tests for {@link FontPreloader}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(
        manifest = Config.NONE,
        shadows = {ShadowSystemClock.class, ShadowResourcesCompat.class})
@LooperMode(Mode.PAUSED)
public class FontPreloaderUnitTest {
    private static final Integer[] FONTS = {
        org.chromium.chrome.R.font.chrome_google_sans,
        org.chromium.chrome.R.font.chrome_google_sans_medium,
        org.chromium.chrome.R.font.chrome_google_sans_bold
    };
    private static final String AFTER_ON_CREATE =
            "Android.Fonts.TimeToRetrieveDownloadableFontsAfterOnCreate";
    private static final String AFTER_INFLATION =
            "Android.Fonts.TimeDownloadableFontsRetrievedAfterPostInflationStartup";
    private static final String BEFORE_INFLATION =
            "Android.Fonts.TimeDownloadableFontsRetrievedBeforePostInflationStartup";
    private static final String BEFORE_FIRST_DRAW =
            "Android.Fonts.TimeDownloadableFontsRetrievedBeforeFirstDraw";
    private static final String AFTER_FIRST_DRAW =
            "Android.Fonts.TimeDownloadableFontsRetrievedAfterFirstDraw";
    private static final String FRE = ".FirstRunActivity";
    private static final String TABBED = ".ChromeTabbedActivity";
    private static final String CUSTOM_TAB = ".CustomTabActivity";
    private static final int INITIAL_TIME = 1000;

    @Mock private Context mContext;

    private FontPreloader mFontPreloader;

    @Implements(ResourcesCompat.class)
    static class ShadowResourcesCompat {
        private static final List<Integer> sFontsRequested = new ArrayList<>();
        private static FontCallback sFontCallback;

        @Resetter
        public static void reset() {
            sFontsRequested.clear();
            sFontCallback = null;
        }

        @Implementation
        public static void getFont(
                Context context, int id, FontCallback fontCallback, Handler handler) {
            sFontCallback = fontCallback;
            sFontsRequested.add(id);
        }

        public static void loadFont() {
            sFontCallback.onFontRetrieved(Typeface.DEFAULT);
        }
    }

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        SystemClock.setCurrentTimeMillis(INITIAL_TIME);
        ShadowResourcesCompat.reset();
        when(mContext.getApplicationContext()).thenReturn(mContext);
        mFontPreloader = new FontPreloader(FONTS);
        mFontPreloader.load(mContext);
    }

    @Test
    public void testGetFontCalledForAllFontsInArray() {
        assertThat(ShadowResourcesCompat.sFontsRequested, containsInAnyOrder(FONTS));
    }

    @Test
    public void testAllFontsRetrievedAfterOnPostInflationStartup_FRE() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
        mFontPreloader.onPostInflationStartupFre();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 228);
        fakeLoadAllFonts();

        assertHistogramRecorded(AFTER_ON_CREATE, 228);
        assertHistogramRecorded(AFTER_INFLATION + FRE, 128);
        assertHistogramNotRecorded(BEFORE_INFLATION + FRE);
        assertHistogramNotRecorded(BEFORE_INFLATION + TABBED);
        assertHistogramNotRecorded(AFTER_INFLATION + TABBED);
        assertHistogramNotRecorded(BEFORE_INFLATION + CUSTOM_TAB);
        assertHistogramNotRecorded(AFTER_INFLATION + CUSTOM_TAB);
    }

    @Test
    public void testAllFontsRetrievedAfterOnPostInflationStartup_Tabbed() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
        mFontPreloader.onPostInflationStartupTabbedActivity();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 200);
        fakeLoadAllFonts();

        assertHistogramRecorded(AFTER_ON_CREATE, 200);
        assertHistogramRecorded(AFTER_INFLATION + TABBED, 100);
        assertHistogramNotRecorded(BEFORE_INFLATION + TABBED);
        assertHistogramNotRecorded(BEFORE_INFLATION + FRE);
        assertHistogramNotRecorded(AFTER_INFLATION + FRE);
        assertHistogramNotRecorded(BEFORE_INFLATION + CUSTOM_TAB);
        assertHistogramNotRecorded(AFTER_INFLATION + CUSTOM_TAB);
    }

    @Test
    public void testAllFontsRetrievedAfterOnPostInflationStartup_CustomTab() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
        mFontPreloader.onPostInflationStartupCustomTabActivity();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 150);
        fakeLoadAllFonts();

        assertHistogramRecorded(AFTER_ON_CREATE, 150);
        assertHistogramRecorded(AFTER_INFLATION + CUSTOM_TAB, 50);
        assertHistogramNotRecorded(BEFORE_INFLATION + CUSTOM_TAB);
        assertHistogramNotRecorded(BEFORE_INFLATION + TABBED);
        assertHistogramNotRecorded(AFTER_INFLATION + TABBED);
        assertHistogramNotRecorded(BEFORE_INFLATION + FRE);
        assertHistogramNotRecorded(AFTER_INFLATION + FRE);
    }

    @Test
    public void testAllFontsRetrievedBeforeOnPostInflationStartup_FRE() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 150);
        fakeLoadAllFonts();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 200);
        mFontPreloader.onPostInflationStartupFre();

        assertHistogramRecorded(AFTER_ON_CREATE, 150);
        assertHistogramRecorded(BEFORE_INFLATION + FRE, 50);
        assertHistogramNotRecorded(BEFORE_INFLATION + TABBED);
        assertHistogramNotRecorded(AFTER_INFLATION + TABBED);
        assertHistogramNotRecorded(AFTER_INFLATION + FRE);
        assertHistogramNotRecorded(BEFORE_INFLATION + CUSTOM_TAB);
        assertHistogramNotRecorded(AFTER_INFLATION + CUSTOM_TAB);
    }

    @Test
    public void testAllFontsRetrievedBeforeOnPostInflationStartup_Tabbed() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 5);
        fakeLoadAllFonts();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 555);
        mFontPreloader.onPostInflationStartupTabbedActivity();

        assertHistogramRecorded(AFTER_ON_CREATE, 5);
        assertHistogramRecorded(BEFORE_INFLATION + TABBED, 550);
        assertHistogramNotRecorded(AFTER_INFLATION + TABBED);
        assertHistogramNotRecorded(BEFORE_INFLATION + FRE);
        assertHistogramNotRecorded(AFTER_INFLATION + FRE);
        assertHistogramNotRecorded(BEFORE_INFLATION + CUSTOM_TAB);
        assertHistogramNotRecorded(AFTER_INFLATION + CUSTOM_TAB);
    }

    @Test
    public void testAllFontsRetrievedBeforeOnPostInflationStartup_CustomTab() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 123);
        fakeLoadAllFonts();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 234);
        mFontPreloader.onPostInflationStartupCustomTabActivity();

        assertHistogramRecorded(AFTER_ON_CREATE, 123);
        assertHistogramRecorded(BEFORE_INFLATION + CUSTOM_TAB, 111);
        assertHistogramNotRecorded(AFTER_INFLATION + CUSTOM_TAB);
        assertHistogramNotRecorded(BEFORE_INFLATION + FRE);
        assertHistogramNotRecorded(AFTER_INFLATION + FRE);
        assertHistogramNotRecorded(BEFORE_INFLATION + TABBED);
        assertHistogramNotRecorded(AFTER_INFLATION + TABBED);
    }

    @Test
    public void testHistogramsNotRecordedBeforeAllFontsLoaded() {
        ShadowResourcesCompat.loadFont();
        ShadowResourcesCompat.loadFont();

        assertHistogramNotRecorded(AFTER_ON_CREATE);
        assertHistogramNotRecorded(BEFORE_INFLATION + TABBED);
        assertHistogramNotRecorded(AFTER_INFLATION + TABBED);
        assertHistogramNotRecorded(BEFORE_INFLATION + FRE);
        assertHistogramNotRecorded(AFTER_INFLATION + FRE);
        assertHistogramNotRecorded(BEFORE_INFLATION + CUSTOM_TAB);
        assertHistogramNotRecorded(AFTER_INFLATION + CUSTOM_TAB);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + FRE);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + FRE);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW);

        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 500);
        ShadowResourcesCompat.loadFont();

        assertHistogramRecorded(AFTER_ON_CREATE, 500);
    }

    @Test
    public void testHistogramRecordedForOnlyFirstActivity_BeforeFREInflation() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 10);
        fakeLoadAllFonts();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 20);
        mFontPreloader.onPostInflationStartupFre();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 30);
        mFontPreloader.onPostInflationStartupTabbedActivity();

        assertHistogramRecorded(BEFORE_INFLATION + FRE, 10);
        assertHistogramNotRecorded(BEFORE_INFLATION + TABBED);
    }

    @Test
    public void testHistogramRecordedForOnlyFirstActivity_AfterFREInflation() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 64);
        mFontPreloader.onPostInflationStartupFre();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 96);
        fakeLoadAllFonts();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 128);
        mFontPreloader.onPostInflationStartupTabbedActivity();

        assertHistogramRecorded(AFTER_INFLATION + FRE, 32);
        assertHistogramNotRecorded(BEFORE_INFLATION + TABBED);
    }

    @Test
    public void testHistogramRecordedForOnlyFirstActivity_BeforeCCTInflation() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 1);
        fakeLoadAllFonts();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 11);
        mFontPreloader.onPostInflationStartupCustomTabActivity();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 111);
        mFontPreloader.onPostInflationStartupTabbedActivity();

        assertHistogramRecorded(BEFORE_INFLATION + CUSTOM_TAB, 10);
        assertHistogramNotRecorded(BEFORE_INFLATION + TABBED);
    }

    @Test
    public void testHistogramRecordedForOnlyFirstActivity_AfterCCTInflation() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 32);
        mFontPreloader.onPostInflationStartupCustomTabActivity();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 64);
        fakeLoadAllFonts();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 128);
        mFontPreloader.onPostInflationStartupTabbedActivity();

        assertHistogramRecorded(AFTER_INFLATION + CUSTOM_TAB, 32);
        assertHistogramNotRecorded(BEFORE_INFLATION + TABBED);
    }

    @Test
    public void testHistogramNotOverEmittedForExtraFontLoads() {
        mFontPreloader.onPostInflationStartupTabbedActivity();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 10);
        fakeLoadAllFonts();

        assertHistogramRecorded(AFTER_ON_CREATE, 10);
        assertHistogramRecorded(AFTER_INFLATION + TABBED, 10);

        // Load an extra font
        ShadowResourcesCompat.loadFont();
        // Still should have only 1 record.
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 10);
        assertHistogramRecorded(AFTER_INFLATION + TABBED, 10);
    }

    @Test
    public void testAllFontsRetrievedAfterFirstDraw_FRE() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
        mFontPreloader.onFirstDrawFre();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 228);
        fakeLoadAllFonts();

        assertHistogramRecorded(AFTER_FIRST_DRAW, 128);
        assertHistogramRecorded(AFTER_FIRST_DRAW + FRE, 128);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + FRE);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB);
    }

    @Test
    public void testAllFontsRetrievedAfterFirstDraw_Tabbed() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
        mFontPreloader.onFirstDrawTabbedActivity();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 113);
        fakeLoadAllFonts();

        assertHistogramRecorded(AFTER_FIRST_DRAW, 13);
        assertHistogramRecorded(AFTER_FIRST_DRAW + TABBED, 13);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + FRE);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + FRE);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB);
    }

    @Test
    public void testAllFontsRetrievedAfterFirstDraw_CustomTab() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 50);
        mFontPreloader.onFirstDrawCustomTabActivity();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
        fakeLoadAllFonts();

        assertHistogramRecorded(AFTER_FIRST_DRAW, 50);
        assertHistogramRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB, 50);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + FRE);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + FRE);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
    }

    @Test
    public void testAllFontsRetrievedBeforeFirstDraw_FRE() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 90);
        fakeLoadAllFonts();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
        mFontPreloader.onFirstDrawFre();

        assertHistogramRecorded(BEFORE_FIRST_DRAW, 10);
        assertHistogramRecorded(BEFORE_FIRST_DRAW + FRE, 10);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + FRE);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB);
    }

    @Test
    public void testAllFontsRetrievedBeforeFirstDraw_Tabbed() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 123);
        fakeLoadAllFonts();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 246);
        mFontPreloader.onFirstDrawTabbedActivity();

        assertHistogramRecorded(BEFORE_FIRST_DRAW, 123);
        assertHistogramRecorded(BEFORE_FIRST_DRAW + TABBED, 123);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + FRE);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + FRE);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB);
    }

    @Test
    public void testAllFontsRetrievedBeforeFirstDraw_CustomTab() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
        fakeLoadAllFonts();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 119);
        mFontPreloader.onFirstDrawCustomTabActivity();

        assertHistogramRecorded(BEFORE_FIRST_DRAW, 19);
        assertHistogramRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB, 19);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + FRE);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + FRE);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
    }

    @Test
    public void testHistogramRecordedForOnlyFirstActivity_BeforeFREDraw() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 10);
        fakeLoadAllFonts();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 20);
        mFontPreloader.onFirstDrawFre();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 30);
        mFontPreloader.onFirstDrawTabbedActivity();

        assertHistogramRecorded(BEFORE_FIRST_DRAW + FRE, 10);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
    }

    @Test
    public void testHistogramRecordedForOnlyFirstActivity_AfterFREDraw() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 10);
        mFontPreloader.onFirstDrawFre();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 20);
        fakeLoadAllFonts();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 30);
        mFontPreloader.onFirstDrawTabbedActivity();

        assertHistogramRecorded(AFTER_FIRST_DRAW + FRE, 10);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
    }

    @Test
    public void testHistogramRecordedForOnlyFirstActivity_BeforeCCTDraw() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
        fakeLoadAllFonts();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 200);
        mFontPreloader.onFirstDrawCustomTabActivity();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 300);
        mFontPreloader.onFirstDrawTabbedActivity();

        assertHistogramRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB, 100);
        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
    }

    @Test
    public void testHistogramRecordedForOnlyFirstActivity_AfterCCTDraw() {
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 111);
        mFontPreloader.onFirstDrawCustomTabActivity();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 222);
        fakeLoadAllFonts();
        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 300);
        mFontPreloader.onFirstDrawTabbedActivity();

        assertHistogramRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB, 111);
        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
    }

    private void fakeLoadAllFonts() {
        for (int i = 0; i < 3; i++) {
            ShadowResourcesCompat.loadFont();
        }
    }

    /**
     * @param histogram Histogram name to assert.
     * @param expectedValue The expected value to be recorded.
     */
    private void assertHistogramRecorded(String histogram, int expectedValue) {
        assertEquals(
                histogram + " isn't recorded correctly.",
                1,
                RecordHistogram.getHistogramValueCountForTesting(histogram, expectedValue));
    }

    /** @param histogram Histogram name to assert. */
    private void assertHistogramNotRecorded(String histogram) {
        assertEquals(
                histogram + " shouldn't be recorded.",
                0,
                RecordHistogram.getHistogramTotalCountForTesting(histogram));
    }
}