chromium/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/AuthenticatorSelectionDialog.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.ui.autofill;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewStub;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.DrawableRes;
import androidx.core.content.res.ResourcesCompat;
import androidx.recyclerview.widget.RecyclerView;

import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.ui.autofill.data.AuthenticatorOption;
import org.chromium.chrome.browser.ui.autofill.internal.R;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.PropertyModel;

import java.util.List;

/**
 * Dialog that presents {@link AuthenticatorOption}s to the user to choose from for fetching credit
 * card information from the backend.
 */
public class AuthenticatorSelectionDialog implements AuthenticatorOptionsAdapter.ItemClickListener {
    private static final int ANIMATION_DURATION_MS = 250;

    /** Interface for the caller to be notified of user actions. */
    public interface Listener {
        /** Notify that the user selected an authenticator option. */
        void onOptionSelected(String authenticatorOptionIdentifier);

        /** Notify that the dialog was dismissed. */
        void onDialogDismissed();
    }

    private final ModalDialogProperties.Controller mModalDialogController =
            new ModalDialogProperties.Controller() {
                @Override
                public void onClick(PropertyModel model, int buttonType) {
                    switch (buttonType) {
                        case ModalDialogProperties.ButtonType.POSITIVE:
                            mListener.onOptionSelected(
                                    mSelectedAuthenticatorOption.getIdentifier());
                            showProgressBarOverlay();
                            break;
                        case ModalDialogProperties.ButtonType.NEGATIVE:
                            mModalDialogManager.dismissDialog(
                                    model, DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
                            break;
                    }
                }

                @Override
                public void onDismiss(PropertyModel model, int dismissalCause) {
                    mListener.onDialogDismissed();
                }
            };

    private final Context mContext;
    private final Listener mListener;
    private final ModalDialogManager mModalDialogManager;
    private View mAuthenticatorSelectionDialogView;
    private View mProgressBarOverlayView;
    private View mAuthenticatorSelectionDialogContentsView;
    private RecyclerView mAuthenticationOptionsRecyclerView;
    private AuthenticatorOptionsAdapter mAuthenticatorOptionsAdapter;

    private PropertyModel mDialogModel;
    private AuthenticatorOption mSelectedAuthenticatorOption;

    public AuthenticatorSelectionDialog(
            Context context, Listener listener, ModalDialogManager modalDialogManager) {
        this.mContext = context;
        this.mListener = listener;
        this.mModalDialogManager = modalDialogManager;
    }

    @Override
    public void onItemClicked(AuthenticatorOption option) {
        mSelectedAuthenticatorOption = option;
        mDialogModel.set(
                ModalDialogProperties.POSITIVE_BUTTON_TEXT,
                getPositiveButtonText(mSelectedAuthenticatorOption.getType()));
    }

    private String getPositiveButtonText(
            @CardUnmaskChallengeOptionType int authenticatorOptionType) {
        switch (authenticatorOptionType) {
            case CardUnmaskChallengeOptionType.SMS_OTP:
            case CardUnmaskChallengeOptionType.EMAIL_OTP:
                return mContext.getResources()
                        .getString(
                                R.string
                                        .autofill_card_unmask_authentication_selection_dialog_ok_button_label_send);
            case CardUnmaskChallengeOptionType.CVC:
                return mContext.getResources()
                        .getString(
                                R.string
                                        .autofill_card_unmask_authentication_selection_dialog_ok_button_label_continue);
            case CardUnmaskChallengeOptionType.UNKNOWN_TYPE:
                // This will never happen.
                assert false
                        : "Attempted to get positive button text for an authenticator option with Unknown type.";
        }
        return "";
    }

    /**
     * Shows an Authenticator Selection dialog.
     *
     * @param authenticatorOptions The authenticator options available to the user.
     */
    public void show(List<AuthenticatorOption> authenticatorOptions) {
        // By default, the first option will be selected.
        mSelectedAuthenticatorOption = authenticatorOptions.get(0);
        mAuthenticatorSelectionDialogView =
                LayoutInflater.from(mContext)
                        .inflate(R.layout.authenticator_selection_dialog, null);

        boolean useCustomTitleView =
                ChromeFeatureList.isEnabled(
                        ChromeFeatureList.AUTOFILL_ENABLE_MOVING_GPAY_LOGO_TO_THE_RIGHT_ON_CLANK);
        int titleIconId =
                useCustomTitleView ? R.drawable.google_pay : R.drawable.google_pay_with_divider;
        String title =
                mContext.getResources()
                        .getString(
                                authenticatorOptions.size() > 1
                                        ? R.string
                                                .autofill_card_auth_selection_dialog_title_multiple_options
                                        : R.string.autofill_card_unmask_verification_title);

        if (useCustomTitleView) {
            ViewStub stub =
                    mAuthenticatorSelectionDialogView.findViewById(R.id.title_with_icon_stub);
            stub.setLayoutResource(R.layout.icon_after_title_view);
            stub.inflate();
        }
        mAuthenticatorSelectionDialogContentsView =
                mAuthenticatorSelectionDialogView.findViewById(
                        R.id.authenticator_selection_dialog_contents);
        mProgressBarOverlayView =
                mAuthenticatorSelectionDialogView.findViewById(R.id.progress_bar_overlay);
        mProgressBarOverlayView.setVisibility(View.GONE);
        // Set up the recycler view.
        mAuthenticationOptionsRecyclerView =
                (RecyclerView)
                        mAuthenticatorSelectionDialogView.findViewById(
                                R.id.authenticator_options_view);
        mAuthenticatorOptionsAdapter =
                new AuthenticatorOptionsAdapter(mContext, authenticatorOptions, this);
        mAuthenticationOptionsRecyclerView.setAdapter(mAuthenticatorOptionsAdapter);
        // Set up the ModalDialog.
        PropertyModel.Builder builder =
                new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
                        .with(ModalDialogProperties.CONTROLLER, mModalDialogController)
                        .with(ModalDialogProperties.CUSTOM_VIEW, mAuthenticatorSelectionDialogView)
                        .with(
                                ModalDialogProperties.NEGATIVE_BUTTON_TEXT,
                                mContext.getResources()
                                        .getString(
                                                R.string
                                                        .autofill_payments_authenticator_selection_dialog_negative_button_label))
                        .with(
                                ModalDialogProperties.POSITIVE_BUTTON_TEXT,
                                getPositiveButtonText(mSelectedAuthenticatorOption.getType()))
                        .with(
                                ModalDialogProperties.BUTTON_STYLES,
                                ModalDialogProperties.ButtonStyles.PRIMARY_FILLED_NEGATIVE_OUTLINE);
        updateTitleView(useCustomTitleView, title, titleIconId, builder);
        mDialogModel = builder.build();
        mModalDialogManager.showDialog(mDialogModel, ModalDialogManager.ModalDialogType.TAB);
    }

    /**
     * Updates the title and icon view. If AUTOFILL_ENABLE_MOVING_GPAY_LOGO_TO_THE_RIGHT_ON_CLANK
     * feature is enabled, sets title and icon in the customView otherwise uses
     * PropertyModel.Builder for title and icon.
     *
     * @param useCustomTitleView Indicates true/false to use custom title view.
     * @param title Title of the prompt dialog.
     * @param titleIcon Icon near the title.
     * @param builder The PropertyModel.Builder instance.
     */
    private void updateTitleView(
            boolean useCustomTitleView,
            String title,
            @DrawableRes int titleIcon,
            PropertyModel.Builder builder) {
        if (useCustomTitleView) {
            TextView titleView =
                    (TextView) mAuthenticatorSelectionDialogView.findViewById(R.id.title);
            titleView.setText(title);
            ImageView iconView =
                    (ImageView) mAuthenticatorSelectionDialogView.findViewById(R.id.title_icon);
            iconView.setImageResource(titleIcon);
        } else {
            builder.with(ModalDialogProperties.TITLE, title);
            builder.with(
                    ModalDialogProperties.TITLE_ICON,
                    ResourcesCompat.getDrawable(
                            mContext.getResources(), titleIcon, mContext.getTheme()));
        }
    }

    /**
     * Dismisses the Authenticator Selection dialog.
     *
     * @param cause The dialog dismissal cause.
     */
    public void dismiss(int cause) {
        mModalDialogManager.dismissDialog(mDialogModel, cause);
    }

    private void showProgressBarOverlay() {
        mProgressBarOverlayView.setVisibility(View.VISIBLE);
        mProgressBarOverlayView.setAlpha(0f);
        mProgressBarOverlayView.animate().alpha(1f).setDuration(ANIMATION_DURATION_MS);
        mAuthenticatorSelectionDialogContentsView
                .animate()
                .alpha(0f)
                .setDuration(ANIMATION_DURATION_MS);
        mDialogModel.set(ModalDialogProperties.POSITIVE_BUTTON_DISABLED, true);
    }
}