chromium/chrome/browser/autofill/android/junit/src/org/chromium/chrome/browser/autofill/options/AutofillOptionsTest.java

// Copyright 2023 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.autofill.options;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import static org.chromium.chrome.browser.autofill.AndroidAutofillAvailabilityStatus.ANDROID_AUTOFILL_SERVICE_IS_GOOGLE;
import static org.chromium.chrome.browser.autofill.AndroidAutofillAvailabilityStatus.ANDROID_VERSION_TOO_OLD;
import static org.chromium.chrome.browser.autofill.AndroidAutofillAvailabilityStatus.AVAILABLE;
import static org.chromium.chrome.browser.autofill.AndroidAutofillAvailabilityStatus.NOT_ALLOWED_BY_POLICY;
import static org.chromium.chrome.browser.autofill.AndroidAutofillAvailabilityStatus.UNKNOWN_ANDROID_AUTOFILL_SERVICE;
import static org.chromium.chrome.browser.autofill.AutofillClientProviderUtils.setAutofillAvailabilityToUseForTesting;
import static org.chromium.chrome.browser.autofill.options.AutofillOptionsProperties.ON_THIRD_PARTY_TOGGLE_CHANGED;
import static org.chromium.chrome.browser.autofill.options.AutofillOptionsProperties.THIRD_PARTY_AUTOFILL_ENABLED;
import static org.chromium.chrome.browser.autofill.options.AutofillOptionsProperties.THIRD_PARTY_TOGGLE_IS_READ_ONLY;

import android.text.SpannableString;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import androidx.annotation.StringRes;
import androidx.fragment.app.testing.FragmentScenario;
import androidx.lifecycle.Lifecycle.Event;
import androidx.lifecycle.LifecycleRegistry;
import androidx.test.filters.SmallTest;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;

import org.chromium.base.FeatureList;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.autofill.options.AutofillOptionsFragment.AutofillOptionsReferrer;
import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncher;
import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncherFactory;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.components.autofill.AutofillFeatures;
import org.chromium.components.browser_ui.settings.TextMessagePreference;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.user_prefs.UserPrefsJni;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modaldialog.ModalDialogProperties.ButtonType;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.text.NoUnderlineClickableSpan;
import org.chromium.ui.text.SpanApplier;

import java.util.Optional;

/** Unit tests for autofill options settings screen. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@EnableFeatures(AutofillFeatures.AUTOFILL_VIRTUAL_VIEW_STRUCTURE_ANDROID)
public class AutofillOptionsTest {

    private static final String SKIP_ALL_CHECKS_PARAM_VALUE = "skip_all_checks";
    private static final String ONLY_SKIP_AWG_CHECK_PARAM_VALUE = "only_skip_awg_check";

    // Shorthand for frequent enums that can't be static imports.
    private static final @RadioButtonGroupThirdPartyPreference.ThirdPartyOption int DEFAULT =
            RadioButtonGroupThirdPartyPreference.ThirdPartyOption.DEFAULT;
    private static final @RadioButtonGroupThirdPartyPreference.ThirdPartyOption int USE_3P =
            RadioButtonGroupThirdPartyPreference.ThirdPartyOption.USE_OTHER_PROVIDER;

    @Rule public JniMocker mJniMocker = new JniMocker();

    @Mock private UserPrefsJni mMockUserPrefsJni;
    @Mock private PrefService mPrefs;
    @Mock private Profile mProfile;
    @Mock private HelpAndFeedbackLauncher mHelpAndFeedbackLauncher;
    @Mock private Runnable mRestartRunnable;
    @Mock private ModalDialogManager mDialogManager;

    @Captor ArgumentCaptor<PropertyModel> mRestartConfirmationDialogModelCaptor;

    private AutofillOptionsFragment mFragment;
    private AutoCloseable mCloseableMocks;
    private FragmentScenario mScenario;

    @Before
    public void setUp() {
        mCloseableMocks = MockitoAnnotations.openMocks(this);

        mJniMocker.mock(UserPrefsJni.TEST_HOOKS, mMockUserPrefsJni);
        doReturn(mPrefs).when(mMockUserPrefsJni).get(mProfile);
        HelpAndFeedbackLauncherFactory.setInstanceForTesting(mHelpAndFeedbackLauncher);

        mScenario =
                FragmentScenario.launchInContainer(
                        AutofillOptionsFragment.class,
                        AutofillOptionsFragment.createRequiredArgs(
                                AutofillOptionsReferrer.SETTINGS),
                        R.style.Theme_MaterialComponents);
        mScenario.onFragment(
                fragment -> {
                    mFragment =
                            (AutofillOptionsFragment)
                                    fragment; // Valid until scenario is recreated.
                    mFragment.setProfile(mProfile);
                });
    }

    @After
    public void tearDown() throws Exception {
        if (mScenario != null) {
            mScenario.close();
        }
        mCloseableMocks.close();
    }

    @Test
    @SmallTest
    public void constructedWithPrefAsDefaultForOption() {
        setAutofillAvailabilityToUseForTesting(AVAILABLE);
        doReturn(true).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);

        // Initializing should set default property but not make use of dialogs or restarts yet.
        PropertyModel model =
                new AutofillOptionsCoordinator(mFragment, this::assertModalNotUsed, Assert::fail)
                        .initializeNow();

        assertTrue(model.get(THIRD_PARTY_AUTOFILL_ENABLED));
        assertTrue(mFragment.getIncognitoNote().isShown());
        assertTrue(getRadioButtonComponent().isEnabled());
        assertHintDisplays(getSpannableString(R.string.autofill_options_hint_3p_setting_ready));
    }

    @Test
    @SmallTest
    public void optionDisabledForAwgUpdatesOnResume() {
        setAutofillAvailabilityToUseForTesting(ANDROID_AUTOFILL_SERVICE_IS_GOOGLE);
        doReturn(false).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);

        // Toggling on resume is to align with prefs and shouldn't trigger restart/dialogs.
        AutofillOptionsCoordinator autofillOptions =
                new AutofillOptionsCoordinator(mFragment, this::assertModalNotUsed, Assert::fail);
        PropertyModel model = autofillOptions.initializeNow();
        LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(mFragment);
        autofillOptions.observeLifecycle(lifecycleRegistry);

        // On construction (assuming Awg is set), the setting is turned off and can't change.
        assertFalse(model.get(THIRD_PARTY_AUTOFILL_ENABLED));
        assertFalse(getRadioButtonComponent().isEnabled());
        assertHintDisplays(getSpannableString(R.string.autofill_options_hint_3p_setting_disabled));

        // On resume, check again whether AwG isn't used anymore — e.g. coming back from Settings.
        setAutofillAvailabilityToUseForTesting(AVAILABLE);
        doReturn(true).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);
        lifecycleRegistry.handleLifecycleEvent(Event.ON_RESUME);

        assertTrue(model.get(THIRD_PARTY_AUTOFILL_ENABLED));
        assertTrue(getRadioButtonComponent().isEnabled());
        assertHintDisplays(getSpannableString(R.string.autofill_options_hint_3p_setting_ready));
    }

    @Test
    @SmallTest
    public void optionDisabledByPolicy() {
        setAutofillAvailabilityToUseForTesting(NOT_ALLOWED_BY_POLICY);
        doReturn(false).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);

        // Toggling on resume is to align with prefs and shouldn't trigger restart/dialogs.
        AutofillOptionsCoordinator autofillOptions =
                new AutofillOptionsCoordinator(mFragment, this::assertModalNotUsed, Assert::fail);
        PropertyModel model = autofillOptions.initializeNow();

        // On construction (assuming Awg is set), the setting is turned off and can't change.
        assertFalse(model.get(THIRD_PARTY_AUTOFILL_ENABLED));
        assertTrue(model.get(THIRD_PARTY_TOGGLE_IS_READ_ONLY));
        assertFalse(getRadioButtonComponent().isEnabled());
        assertHintDisplays(getString(R.string.autofill_options_hint_policy));
    }

    @Test
    @SmallTest
    public void optionEnabledWithSpecialOverrideForAwg() {
        setAutofillAvailabilityToUseForTesting(ANDROID_AUTOFILL_SERVICE_IS_GOOGLE);
        addFeatureOverrideToSkipChecks(ONLY_SKIP_AWG_CHECK_PARAM_VALUE);
        doReturn(false).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);

        // Toggling on resume is to align with prefs and shouldn't trigger restart/dialogs.
        PropertyModel model =
                new AutofillOptionsCoordinator(mFragment, this::assertModalNotUsed, Assert::fail)
                        .initializeNow();

        // On construction (assuming Awg is set), the setting is turned off but may change.
        assertFalse(model.get(THIRD_PARTY_AUTOFILL_ENABLED));
        assertTrue(getRadioButtonComponent().isEnabled());
        assertHintDisplays(getSpannableString(R.string.autofill_options_hint_3p_setting_ready));
    }

    @Test
    @SmallTest
    public void overrideForAwgDoesntAllowOtherChecksToBeSkipped() {
        setAutofillAvailabilityToUseForTesting(ANDROID_VERSION_TOO_OLD);
        addFeatureOverrideToSkipChecks(ONLY_SKIP_AWG_CHECK_PARAM_VALUE);
        doReturn(false).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);

        // Toggling on resume is to align with prefs and shouldn't trigger restart/dialogs.
        PropertyModel model =
                new AutofillOptionsCoordinator(mFragment, this::assertModalNotUsed, Assert::fail)
                        .initializeNow();

        // On construction, the setting is turned off and can't change.
        assertFalse(model.get(THIRD_PARTY_AUTOFILL_ENABLED));
        assertFalse(getRadioButtonComponent().isEnabled());
    }

    @Test
    @SmallTest
    public void genericOverrideAllowsOtherChecksToBeSkipped() {
        setAutofillAvailabilityToUseForTesting(UNKNOWN_ANDROID_AUTOFILL_SERVICE);
        addFeatureOverrideToSkipChecks(SKIP_ALL_CHECKS_PARAM_VALUE);

        // Toggling on resume is to align with prefs and shouldn't trigger restart/dialogs.
        PropertyModel model =
                new AutofillOptionsCoordinator(mFragment, this::assertModalNotUsed, Assert::fail)
                        .initializeNow();

        // On construction, the setting is turned off but can change.
        assertFalse(model.get(THIRD_PARTY_AUTOFILL_ENABLED));
        assertTrue(getRadioButtonComponent().isEnabled());
    }

    @Test
    @SmallTest
    public void optionEnabledWithGenericOverrideForAwg() {
        setAutofillAvailabilityToUseForTesting(ANDROID_AUTOFILL_SERVICE_IS_GOOGLE);
        addFeatureOverrideToSkipChecks(SKIP_ALL_CHECKS_PARAM_VALUE);
        doReturn(false).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);

        // Toggling on resume is to align with prefs and shouldn't trigger restart/dialogs.
        PropertyModel model =
                new AutofillOptionsCoordinator(mFragment, this::assertModalNotUsed, Assert::fail)
                        .initializeNow();

        // On construction (assuming Awg is set), the setting is turned off but may change.
        assertFalse(model.get(THIRD_PARTY_AUTOFILL_ENABLED));
        assertTrue(getRadioButtonComponent().isEnabled());
        assertHintDisplays(getSpannableString(R.string.autofill_options_hint_3p_setting_ready));
    }

    @Test
    @SmallTest
    public void optionEnabledToSwitchOffAwg() {
        setAutofillAvailabilityToUseForTesting(ANDROID_AUTOFILL_SERVICE_IS_GOOGLE);
        doReturn(true).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);

        // Toggling on resume is to align with prefs and shouldn't trigger restart/dialogs.
        PropertyModel model =
                new AutofillOptionsCoordinator(mFragment, this::assertModalNotUsed, Assert::fail)
                        .initializeNow();

        // On construction (assuming Awg is set), the setting is turned off but may change.
        assertTrue(model.get(THIRD_PARTY_AUTOFILL_ENABLED));
        assertFalse(model.get(THIRD_PARTY_TOGGLE_IS_READ_ONLY));
        assertTrue(getRadioButtonComponent().isEnabled());
        assertHintDisplays(getSpannableString(R.string.autofill_options_hint_3p_setting_ready));
    }

    @Test
    @SmallTest
    public void toggledOptionRecordedInHistogram() {
        HistogramWatcher histogramWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        AutofillOptionsMediator.HISTOGRAM_USE_THIRD_PARTY_FILLING, true);
        AutofillOptionsCoordinator autofillOptions =
                new AutofillOptionsCoordinator(mFragment, () -> mDialogManager, mRestartRunnable);
        PropertyModel model = autofillOptions.initializeNow();

        // Enabling the option should be recorded once.
        getRadioButtonComponent().getOptInButton().performClick();
        verifyAndDismissDialogManager(Optional.of(ButtonType.POSITIVE));
        verify(mPrefs).setBoolean(eq(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE), eq(true));
        histogramWatcher.assertExpected();

        // Enabling the option again should be ignored.
        model.get(ON_THIRD_PARTY_TOGGLE_CHANGED).onResult(true);
        histogramWatcher.assertExpected();

        // Disabling the option should be recorded again.
        reset(mDialogManager);
        histogramWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        AutofillOptionsMediator.HISTOGRAM_USE_THIRD_PARTY_FILLING, false);
        getRadioButtonComponent().getDefaultButton().performClick();
        verifyAndDismissDialogManager(Optional.of(ButtonType.POSITIVE));
        verify(mPrefs).setBoolean(eq(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE), eq(false));
        histogramWatcher.assertExpected();

        verify(mRestartRunnable, times(2)).run(); // For enabling and disabling.
    }

    @Test
    @SmallTest
    public void updateSettingsFromPrefOnViewCreated() {
        doReturn(true).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);
        assertEquals(getRadioButtonComponent().getSelectedOption(), DEFAULT); // Not updated!

        // Update on initial binding. Fail if that triggers the dialog or restarting!
        AutofillOptionsCoordinator.createFor(mFragment, this::assertModalNotUsed, Assert::fail);

        verifyOptionReflectedInView(USE_3P);
    }

    @Test
    @SmallTest
    public void toggledOptionSetsPrefAndRestarts() {
        doReturn(false).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);
        PropertyModel model =
                new AutofillOptionsCoordinator(mFragment, () -> mDialogManager, mRestartRunnable)
                        .initializeNow();
        assertFalse(model.get(THIRD_PARTY_AUTOFILL_ENABLED)); // Not updated yet!

        getRadioButtonComponent().getOptInButton().performClick();

        verifyAndDismissDialogManager(Optional.of(ButtonType.POSITIVE));

        verify(mPrefs).setBoolean(eq(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE), eq(true));
        assertTrue(model.get(THIRD_PARTY_AUTOFILL_ENABLED));
        verifyOptionReflectedInView(USE_3P);
        verify(mRestartRunnable).run();
    }

    @Test
    @SmallTest
    public void toggledOptionResetsWithoutConfirmation() {
        doReturn(false).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);
        PropertyModel model =
                new AutofillOptionsCoordinator(mFragment, () -> mDialogManager, mRestartRunnable)
                        .initializeNow();
        assertFalse(model.get(THIRD_PARTY_AUTOFILL_ENABLED)); // Not updated yet!

        getRadioButtonComponent().getOptInButton().performClick();

        verifyAndDismissDialogManager(Optional.of(ButtonType.NEGATIVE));

        verify(mPrefs, times(0))
                .setBoolean(eq(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE), anyBoolean());
        assertFalse(model.get(THIRD_PARTY_AUTOFILL_ENABLED));
        verifyOptionReflectedInView(DEFAULT);
        verify(mRestartRunnable, times(0)).run();
    }

    @Test
    @SmallTest
    public void toggledOptionResetsWhenDismissed() {
        doReturn(false).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);
        PropertyModel model =
                new AutofillOptionsCoordinator(mFragment, () -> mDialogManager, mRestartRunnable)
                        .initializeNow();
        assertFalse(model.get(THIRD_PARTY_AUTOFILL_ENABLED)); // Not updated yet!

        getRadioButtonComponent().getOptInButton().performClick();

        verifyAndDismissDialogManager();

        verify(mPrefs, times(0))
                .setBoolean(eq(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE), anyBoolean());
        assertFalse(model.get(THIRD_PARTY_AUTOFILL_ENABLED));
        verifyOptionReflectedInView(DEFAULT);
        verify(mRestartRunnable, times(0)).run();
    }

    @Test
    @SmallTest
    public void setPrefTogglesOptionOnResume() {
        doReturn(false).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);
        // Toggling on resume is to align with prefs and shouldn't trigger restart/dialogs.
        AutofillOptionsCoordinator autofillOptions =
                new AutofillOptionsCoordinator(mFragment, this::assertModalNotUsed, Assert::fail);
        PropertyModel model = autofillOptions.initializeNow();
        LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(mFragment);
        autofillOptions.observeLifecycle(lifecycleRegistry);
        assertFalse(model.get(THIRD_PARTY_AUTOFILL_ENABLED));

        doReturn(true).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);
        lifecycleRegistry.handleLifecycleEvent(Event.ON_RESUME);

        assertTrue(model.get(THIRD_PARTY_AUTOFILL_ENABLED));
        verifyOptionReflectedInView(USE_3P);
    }

    @Test
    @SmallTest
    public void suppliesTitle() {
        AutofillOptionsCoordinator.createFor(mFragment, this::assertModalNotUsed, Assert::fail);

        assertEquals(mFragment.getPageTitle().get(), getString(R.string.autofill_options_title));
    }

    @Test
    @SmallTest
    public void setsPref() {
        // Update on initial binding. Shouldn't trigger dialogs or restart.
        AutofillOptionsCoordinator.createFor(mFragment, this::assertModalNotUsed, Assert::fail);

        assertEquals(
                getRadioButtonComponent().getKey(),
                AutofillOptionsFragment.PREF_AUTOFILL_THIRD_PARTY_FILLING);
        assertEquals(
                getRadioButtonComponent().getDefaultButton().getPrimaryText(),
                getString(R.string.autofill_third_party_filling_default));
        assertEquals(
                getRadioButtonComponent().getDefaultButton().getDescriptionText(),
                getString(R.string.autofill_third_party_filling_default_description));
        assertEquals(
                getRadioButtonComponent().getOptInButton().getPrimaryText(),
                getString(R.string.autofill_third_party_filling_opt_in));
        assertEquals(
                getRadioButtonComponent().getOptInButton().getDescriptionText(),
                getString(R.string.autofill_third_party_filling_opt_in_description));
    }

    @Test
    @SmallTest
    public void injectedHelpTriggersAutofillHelp() {
        Menu helpMenu = mock(Menu.class);
        MenuItem helpItem = mock(MenuItem.class);
        doReturn(helpItem)
                .when(helpMenu)
                .add(Menu.NONE, R.id.menu_id_targeted_help, Menu.NONE, R.string.menu_help);
        doReturn(R.id.menu_id_targeted_help).when(helpItem).getItemId();

        // Create completely replaces the menu with only the help icon.
        mFragment.onCreateOptionsMenu(helpMenu, mock(MenuInflater.class));
        verify(helpMenu).clear();
        verify(helpItem).setIcon(R.drawable.ic_help_and_feedback);

        // Trigger the help as it would happen on tap.
        mFragment.onOptionsItemSelected(helpItem);
        verify(mHelpAndFeedbackLauncher)
                .show(mFragment.getActivity(), getString(R.string.help_context_autofill), null);
    }

    @Test
    @SmallTest
    public void passedReferrerRecordedInHistogram() {
        HistogramWatcher histogramWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        AutofillOptionsMediator.HISTOGRAM_REFERRER,
                        AutofillOptionsReferrer.SETTINGS);

        // Component initialization triggers the recording.
        AutofillOptionsCoordinator.createFor(mFragment, this::assertModalNotUsed, Assert::fail);

        histogramWatcher.assertExpected();
    }

    private ModalDialogManager assertModalNotUsed() {
        fail("The modal dialog manager shouldn't have been used yet!");
        return null;
    }

    private String getString(@StringRes int stringId) {
        return mFragment.getResources().getString(stringId);
    }

    private SpannableString getSpannableString(@StringRes int stringId) {
        return SpanApplier.applySpans(
                getString(stringId),
                new SpanApplier.SpanInfo(
                        "<link>",
                        "</link>",
                        new NoUnderlineClickableSpan(
                                mFragment.getContext(), unusedView -> fail())));
    }

    private void verifyOptionReflectedInView(
            @RadioButtonGroupThirdPartyPreference.ThirdPartyOption int selectedOption) {
        assert selectedOption == DEFAULT || selectedOption == USE_3P;
        assertNotNull(getRadioButtonComponent());
        boolean uses_third_party = selectedOption == USE_3P;
        assertEquals(getRadioButtonComponent().getSelectedOption(), selectedOption);
        assertEquals(getRadioButtonComponent().getDefaultButton().isChecked(), !uses_third_party);
        assertEquals(getRadioButtonComponent().getOptInButton().isChecked(), uses_third_party);
    }

    /** {@see verifyAndDismissDialogManager(Optional<Integer> optButtonToClick)} */
    private void verifyAndDismissDialogManager() {
        verifyAndDismissDialogManager(Optional.empty());
    }

    /**
     * Checks the mock was called. Captures the triggered dialog model and dismisses it. If given,
     * the it emulates a click on {@link optButtonToClick}. Otherwise, it calls {@code onDismiss}.
     *
     * @param optButtonToClick An optional containing a {@link ButtonType}.
     */
    private void verifyAndDismissDialogManager(Optional<Integer> optButtonToClick) {
        verify(mDialogManager)
                .showDialog(
                        mRestartConfirmationDialogModelCaptor.capture(), eq(ModalDialogType.APP));
        PropertyModel model = mRestartConfirmationDialogModelCaptor.getValue();
        ModalDialogProperties.Controller mediator = model.get(ModalDialogProperties.CONTROLLER);
        if (optButtonToClick.isEmpty()) {
            mediator.onDismiss(model, DialogDismissalCause.NAVIGATE_BACK);
            return;
        }
        assertNotNull(optButtonToClick.get());
        mediator.onClick(model, optButtonToClick.get());
        if (optButtonToClick.get() == ButtonType.NEGATIVE) {
            verify(mDialogManager)
                    .dismissDialog(eq(model), eq(DialogDismissalCause.NEGATIVE_BUTTON_CLICKED));
            mediator.onDismiss(model, DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
        }
    }

    private RadioButtonGroupThirdPartyPreference getRadioButtonComponent() {
        assertNotNull(mFragment);
        return mFragment.getThirdPartyFillingOption();
    }

    private void assertHintDisplays(CharSequence message) {
        assertNotNull(mFragment);
        TextMessagePreference hint = mFragment.getHint();
        assertTrue(hint.isShown());
        assertNotNull(hint.getSummary());
        assertEquals(message.toString(), hint.getSummary().toString());
    }

    private void addFeatureOverrideToSkipChecks(String checksToSkip) {
        FeatureList.TestValues testValues = new FeatureList.TestValues();
        testValues.addFeatureFlagOverride(
                ChromeFeatureList.AUTOFILL_VIRTUAL_VIEW_STRUCTURE_ANDROID, true);
        testValues.addFieldTrialParamOverride(
                ChromeFeatureList.AUTOFILL_VIRTUAL_VIEW_STRUCTURE_ANDROID,
                "skip_compatibility_check",
                checksToSkip);
        FeatureList.setTestValues(testValues);
    }
}