chromium/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/options/AutofillOptionsCoordinator.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.chromium.chrome.browser.autofill.options.AutofillOptionsProperties.ON_THIRD_PARTY_TOGGLE_CHANGED;

import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.Observer;

import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.autofill.R;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.components.autofill.AutofillFeatures;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;

/**
 * Coordinator for the autofill options settings screen. Connects the settings fragment with ...
 *   ... a model keeping track of the settings state, and
 *   ... a mediator to ensure the settings UI is consistent with prefs.
 */
public class AutofillOptionsCoordinator {
    final AutofillOptionsFragment mFragment;
    final AutofillOptionsMediator mMediator;
    final Observer<LifecycleOwner> mFragmentLifeCycleOwnerObserver = this::onLifeCycleOwnerChanged;
    final LifecycleObserver mFragmentLifeCycleObserver =
            new DefaultLifecycleObserver() {
                @Override
                public void onResume(LifecycleOwner lifecycleOwner) {
                    mMediator.updateToggleStateFromPref();
                }

                @Override
                public void onDestroy(LifecycleOwner lifecycleOwner) {
                    lifecycleOwner.getLifecycle().removeObserver(this);
                }
            };

    /**
     * Creates a new coordinator and attaches it to the fragment. Waits until the fragment is ready
     * before completing initialization.
     *
     * @param fragment An @{link AutofillOptionsFragment} hosting all settings.
     * @param restartRunnable A @{link Runnable} to restart Chrome when settings change.
     */
    public static void createFor(
            AutofillOptionsFragment fragment,
            Supplier<ModalDialogManager> modalDialogManagerSupplier,
            Runnable restartRunnable) {
        new AutofillOptionsCoordinator(fragment, modalDialogManagerSupplier, restartRunnable)
                .initializeOnViewCreated();
    }

    @VisibleForTesting
    AutofillOptionsCoordinator(
            AutofillOptionsFragment fragment,
            Supplier<ModalDialogManager> modalDialogManagerSupplier,
            Runnable restartRunnable) {
        assert ChromeFeatureList.isEnabled(
                AutofillFeatures.AUTOFILL_VIRTUAL_VIEW_STRUCTURE_ANDROID);
        mFragment = fragment;
        mMediator =
                new AutofillOptionsMediator(
                        mFragment.getProfile(),
                        modalDialogManagerSupplier,
                        this::buildRestartConfirmationDialog,
                        restartRunnable);
    }

    /**
     * Create the model and MCP with {@link initializeNow} once the view is created.
     *
     * <p>The view's lifecycle is not available at this point, so observe the {@link LiveData} for
     * it to get notified when {@link onCreateView} is called. Then stop observing the lifecycle
     * owner and start observing the view lifecycle.
     */
    private void initializeOnViewCreated() {
        mFragment
                .getViewLifecycleOwnerLiveData()
                .observe(mFragment, mFragmentLifeCycleOwnerObserver);
    }

    /**
     * Creates the model and returns it for testing. Assigns the model to the mediator and connects
     * the view to the model.
     */
    @VisibleForTesting
    PropertyModel initializeNow() {
        PropertyModel model =
                new PropertyModel.Builder(AutofillOptionsProperties.ALL_KEYS)
                        .with(ON_THIRD_PARTY_TOGGLE_CHANGED, mMediator::onThirdPartyToggleChanged)
                        .build();
        mMediator.initialize(model, mFragment.getReferrer(), mFragment.getContext());

        PropertyModelChangeProcessor.create(model, mFragment, AutofillOptionsViewBinder::bind);
        return model;
    }

    private void onLifeCycleOwnerChanged(LifecycleOwner lifecycleOwner) {
        if (lifecycleOwner == null) {
            return; // Ignore events before {@link onCreateView} creates a new lifeCycleOwner.
        }
        // If it hasn't happened yet, initialize all subcomponents with the available view now.
        if (!mMediator.isInitialized()) {
            mFragment
                    .getViewLifecycleOwnerLiveData()
                    .removeObserver(mFragmentLifeCycleOwnerObserver);
            initializeNow();
            observeLifecycle(lifecycleOwner.getLifecycle());
        }
    }

    private PropertyModel buildRestartConfirmationDialog() {
        return new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
                .with(
                        ModalDialogProperties.TITLE,
                        getString(R.string.autofill_options_restart_prompt_title))
                .with(
                        ModalDialogProperties.MESSAGE_PARAGRAPH_1,
                        getString(R.string.autofill_options_restart_prompt_text))
                .with(
                        ModalDialogProperties.POSITIVE_BUTTON_TEXT,
                        getString(R.string.autofill_options_confirm_restart))
                .with(
                        ModalDialogProperties.NEGATIVE_BUTTON_TEXT,
                        getString(R.string.autofill_options_undo_toggle_change))
                .with(ModalDialogProperties.CANCEL_ON_TOUCH_OUTSIDE, true)
                .with(ModalDialogProperties.CONTROLLER, mMediator)
                .build();
    }

    @VisibleForTesting
    void observeLifecycle(Lifecycle lifecycle) {
        lifecycle.addObserver(mFragmentLifeCycleObserver);
    }

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