chromium/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillServerCardEditor.java

// Copyright 2016 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.settings;

import static org.chromium.chrome.browser.autofill.AutofillUiUtils.getCardIcon;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.IntDef;
import androidx.annotation.Nullable;

import org.chromium.base.Callback;
import org.chromium.base.CommandLine;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.build.annotations.UsedByReflection;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.autofill.AutofillUiUtils;
import org.chromium.chrome.browser.autofill.PersonalDataManagerFactory;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.components.autofill.AutofillProfile;
import org.chromium.components.autofill.VirtualCardEnrollmentLinkType;
import org.chromium.components.autofill.VirtualCardEnrollmentState;
import org.chromium.components.browser_ui.modaldialog.AppModalPresenter;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/** Server credit card settings. */
public class AutofillServerCardEditor extends AutofillCreditCardEditor {
    private static final String AUTOFILL_MANAGE_PAYMENTS_CARDS_URL =
            "https://pay.google.com/pay?p=paymentmethods&utm_source=chrome&utm_medium=settings&utm_campaign=payment_methods";
    private static final String AUTOFILL_MANAGE_PAYMENTS_CARDS_SANDBOX_URL =
            "https://pay.sandbox.google.com/pay?p=paymentmethods&utm_source=chrome&utm_medium=settings&utm_campaign=payment_methods";
    private static final String SETTINGS_PAGE_ENROLLMENT_HISTOGRAM_TEXT =
            "Autofill.VirtualCard.SettingsPageEnrollment";

    private TextView mVirtualCardEnrollmentButton;
    private boolean mVirtualCardEnrollmentButtonShowsUnenroll;
    private AutofillPaymentMethodsDelegate mDelegate;
    private boolean mAwaitingUpdateVirtualCardEnrollmentResponse;
    private boolean mServerCardEditorClosed;
    private Callback<Boolean> mVirtualCardEnrollmentUpdateResponseCallback;
    private Callback<String> mServerCardEditLinkOpenerCallback =
            url -> CustomTabActivity.showInfoPage(getActivity(), url);

    // Enum to represent the types of cards that show info in a server card editor page.
    @IntDef({CardType.SERVER_CARD, CardType.VIRTUAL_CARD})
    @Retention(RetentionPolicy.SOURCE)
    private @interface CardType {
        int SERVER_CARD = 1;
        int VIRTUAL_CARD = 2;
    }

    // Enum to represent the buttons in a server card editor page.
    @IntDef({
        ButtonType.EDIT_CARD,
        ButtonType.VIRTUAL_CARD_ENROLL,
        ButtonType.VIRTUAL_CARD_UNENROLL
    })
    @Retention(RetentionPolicy.SOURCE)
    private @interface ButtonType {
        int EDIT_CARD = 1;
        int VIRTUAL_CARD_ENROLL = 2;
        int VIRTUAL_CARD_UNENROLL = 3;
    }

    private static String getCardType(@CardType int type) {
        switch (type) {
            case CardType.SERVER_CARD:
                return "ServerCard";
            case CardType.VIRTUAL_CARD:
                return "VirtualCard";
            default:
                return "None";
        }
    }

    private static String getButtonType(@ButtonType int type) {
        switch (type) {
            case ButtonType.EDIT_CARD:
                return "EditCard";
            case ButtonType.VIRTUAL_CARD_ENROLL:
                return "VirtualCardEnroll";
            case ButtonType.VIRTUAL_CARD_UNENROLL:
                return "VirtualCardUnenroll";
            default:
                return "None";
        }
    }

    private static void logServerCardEditorButtonClicks(
            @CardType int cardType, @ButtonType int buttonType) {
        RecordHistogram.recordBooleanHistogram(
                "Autofill.SettingsPage.ButtonClicked."
                        + getCardType(cardType)
                        + "."
                        + getButtonType(buttonType),
                true);
    }

    private static void logSettingsPageEnrollmentDialogUserSelection(boolean accepted) {
        RecordHistogram.recordBooleanHistogram(SETTINGS_PAGE_ENROLLMENT_HISTOGRAM_TEXT, accepted);
    }

    @UsedByReflection("AutofillPaymentMethodsFragment.java")
    public AutofillServerCardEditor() {}

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mDelegate = new AutofillPaymentMethodsDelegate(getProfile());
        mVirtualCardEnrollmentUpdateResponseCallback =
                isUpdateSuccessful -> {
                    // If the server card editor page was closed when the server call was in
                    // progress, cleanup the delegate. Else, update the enrollment button.
                    if (mServerCardEditorClosed) {
                        mDelegate.cleanup();
                    } else {
                        // Mark completion of the server call.
                        mAwaitingUpdateVirtualCardEnrollmentResponse = false;
                        if (isUpdateSuccessful) {
                            // Update the button label.
                            setVirtualCardEnrollmentButtonLabel(
                                    !mVirtualCardEnrollmentButtonShowsUnenroll);
                        } else {
                            // If update was not successful, enable the button so users can try
                            // again.
                            mVirtualCardEnrollmentButton.setEnabled(true);
                        }
                    }
                };
    }

    @Override
    public View onCreateView(
            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        final View v = super.onCreateView(inflater, container, savedInstanceState);
        if (mCard == null) {
            getActivity().finish();
            return v;
        }

        // Set card icon. It can be either a custom card art or the network icon.
        ImageView cardIconContainer = v.findViewById(R.id.settings_page_card_icon);
        cardIconContainer.setImageDrawable(
                getCardIcon(
                        getContext(),
                        PersonalDataManagerFactory.getForProfile(getProfile()),
                        mCard.getCardArtUrl(),
                        mCard.getIssuerIconDrawableId(),
                        AutofillUiUtils.CardIconSize.LARGE,
                        ChromeFeatureList.isEnabled(
                                ChromeFeatureList.AUTOFILL_ENABLE_CARD_ART_IMAGE)));

        ((TextView) v.findViewById(R.id.settings_page_card_name))
                .setText(mCard.getCardNameForAutofillDisplay());
        ((TextView) v.findViewById(R.id.card_last_four))
                .setText(mCard.getObfuscatedLastFourDigits());
        ((TextView) v.findViewById(R.id.settings_page_card_expiration))
                .setText(mCard.getFormattedExpirationDate(getActivity()));
        v.findViewById(R.id.edit_server_card)
                .setOnClickListener(
                        view -> {
                            logServerCardEditorButtonClicks(
                                    showVirtualCardEnrollmentButton()
                                            ? CardType.VIRTUAL_CARD
                                            : CardType.SERVER_CARD,
                                    ButtonType.EDIT_CARD);
                            mServerCardEditLinkOpenerCallback.onResult(getEditCardLink());
                        });

        final LinearLayout virtualCardContainerLayout = v.findViewById(R.id.virtual_card_ui);
        mVirtualCardEnrollmentButton = v.findViewById(R.id.virtual_card_enrollment_button);
        if (showVirtualCardEnrollmentButton()) {
            virtualCardContainerLayout.setVisibility(View.VISIBLE);
            setVirtualCardEnrollmentButtonLabel(
                    mCard.getVirtualCardEnrollmentState() == VirtualCardEnrollmentState.ENROLLED);
            mVirtualCardEnrollmentButton.setOnClickListener(
                    view -> {
                        assert mDelegate != null
                                : "mDelegate must be initialized before making (un)enrolment"
                                        + " calls.";
                        final ModalDialogManager modalDialogManager =
                                new ModalDialogManager(
                                        new AppModalPresenter(getActivity()), ModalDialogType.APP);
                        logServerCardEditorButtonClicks(
                                CardType.VIRTUAL_CARD,
                                mVirtualCardEnrollmentButtonShowsUnenroll
                                        ? ButtonType.VIRTUAL_CARD_UNENROLL
                                        : ButtonType.VIRTUAL_CARD_ENROLL);
                        // TODO(@vishwasuppoor): Show a blocking progress dialog
                        // (crbug.com/1327467).
                        // Disable the button until we receive a response from the server.
                        mVirtualCardEnrollmentButton.setEnabled(false);
                        if (!mVirtualCardEnrollmentButtonShowsUnenroll) {
                            mDelegate.initVirtualCardEnrollment(
                                    mCard.getInstrumentId(),
                                    result ->
                                            showVirtualCardEnrollmentDialog(
                                                    result, modalDialogManager));
                        } else {
                            showVirtualCardUnenrollmentDialog(modalDialogManager);
                        }
                    });
        } else {
            virtualCardContainerLayout.setVisibility(View.GONE);
        }

        initializeButtons(v);
        return v;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // Ensure that the native AutofillPaymentMethodsDelegateMobile instance is cleaned up.
        // If a server call is in progress, do not cleanup the delegate yet.
        if (mAwaitingUpdateVirtualCardEnrollmentResponse) {
            // Mark that the server card editor page was closed, so when the server call is
            // completed, the delegate can be cleaned up.
            mServerCardEditorClosed = true;
            return;
        }
        mDelegate.cleanup();
    }

    private void showVirtualCardEnrollmentDialog(
            VirtualCardEnrollmentFields virtualCardEnrollmentFields,
            ModalDialogManager modalDialogManager) {
        AutofillVirtualCardEnrollmentDialog.LinkClickCallback onLinkClicked =
                (url, virtualCardEnrollmentLinkType) -> {
                    RecordHistogram.recordEnumeratedHistogram(
                            SETTINGS_PAGE_ENROLLMENT_HISTOGRAM_TEXT + ".LinkClicked",
                            virtualCardEnrollmentLinkType,
                            VirtualCardEnrollmentLinkType.MAX_VALUE + 1);
                    CustomTabActivity.showInfoPage(getActivity(), url);
                };
        Callback<Integer> resultHandler =
                dismissalCause -> {
                    if (dismissalCause == DialogDismissalCause.POSITIVE_BUTTON_CLICKED) {
                        logSettingsPageEnrollmentDialogUserSelection(true);
                        // Silently enroll the virtual card.
                        mDelegate.enrollOfferedVirtualCard(
                                mVirtualCardEnrollmentUpdateResponseCallback);
                        // Turn the flag on indicating that a server call is in progress.
                        mAwaitingUpdateVirtualCardEnrollmentResponse = true;
                    } else {
                        logSettingsPageEnrollmentDialogUserSelection(false);
                        // Since the user canceled the enrollment dialog, enable the button
                        // again to allow for enrollment.
                        mVirtualCardEnrollmentButton.setEnabled(true);
                    }
                };
        AutofillVirtualCardEnrollmentDialog dialog =
                new AutofillVirtualCardEnrollmentDialog(
                        getActivity(),
                        modalDialogManager,
                        PersonalDataManagerFactory.getForProfile(getProfile()),
                        virtualCardEnrollmentFields,
                        getActivity()
                                .getString(
                                        R.string
                                                .autofill_virtual_card_enrollment_accept_button_label),
                        getActivity().getString(R.string.no_thanks),
                        onLinkClicked,
                        resultHandler);
        dialog.show();
    }

    private void showVirtualCardUnenrollmentDialog(ModalDialogManager modalDialogManager) {
        AutofillVirtualCardUnenrollmentDialog dialog =
                new AutofillVirtualCardUnenrollmentDialog(
                        getActivity(),
                        modalDialogManager,
                        unenrollRequested -> {
                            if (unenrollRequested) {
                                mDelegate.unenrollVirtualCard(
                                        mCard.getInstrumentId(),
                                        mVirtualCardEnrollmentUpdateResponseCallback);
                                // Turn the flag on indicating that a server call is in
                                // progress.
                                mAwaitingUpdateVirtualCardEnrollmentResponse = true;
                            } else {
                                mVirtualCardEnrollmentButton.setEnabled(true);
                            }
                        });
        dialog.show();
    }

    private boolean showVirtualCardEnrollmentButton() {
        return (mCard.getVirtualCardEnrollmentState() == VirtualCardEnrollmentState.ENROLLED
                || mCard.getVirtualCardEnrollmentState()
                        == VirtualCardEnrollmentState.UNENROLLED_AND_ELIGIBLE);
    }

    /** Updates the Virtual Card Enrollment button label. */
    private void setVirtualCardEnrollmentButtonLabel(boolean isEnrolled) {
        mVirtualCardEnrollmentButtonShowsUnenroll = isEnrolled;
        mVirtualCardEnrollmentButton.setEnabled(true);
        mVirtualCardEnrollmentButton.setText(
                mVirtualCardEnrollmentButtonShowsUnenroll
                        ? R.string.autofill_card_editor_virtual_card_turn_off_button_label
                        : R.string.autofill_card_editor_virtual_card_turn_on_button_label);
    }

    // Returns the URL for managing the card in GPay Web.
    private String getEditCardLink() {
        // Check if sandbox is enabled.
        if (CommandLine.getInstance().hasSwitch(ChromeSwitches.USE_SANDBOX_WALLET_ENVIRONMENT)) {
            return new StringBuilder(AUTOFILL_MANAGE_PAYMENTS_CARDS_SANDBOX_URL)
                    .append("&id=")
                    .append(mCard.getInstrumentId())
                    .toString();
        }
        return new StringBuilder(AUTOFILL_MANAGE_PAYMENTS_CARDS_URL)
                .append("&id=")
                .append(mCard.getInstrumentId())
                .toString();
    }

    @Override
    protected int getLayoutId() {
        return R.layout.autofill_server_card_editor;
    }

    @Override
    protected int getTitleResourceId(boolean isNewEntry) {
        return R.string.autofill_edit_credit_card;
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        if (parent == mBillingAddress && position != mInitialBillingAddressPos) {
            ((Button) getView().findViewById(R.id.button_primary)).setEnabled(true);
        }
    }

    @Override
    protected boolean saveEntry() {
        if (mBillingAddress.getSelectedItem() != null
                && mBillingAddress.getSelectedItem() instanceof AutofillProfile) {
            mCard.setBillingAddressId(
                    ((AutofillProfile) mBillingAddress.getSelectedItem()).getGUID());
            PersonalDataManagerFactory.getForProfile(getProfile())
                    .updateServerCardBillingAddress(mCard);
        }
        return true;
    }

    @Override
    protected boolean getIsDeletable() {
        return false;
    }

    public void setServerCardEditLinkOpenerCallbackForTesting(Callback<String> callback) {
        mServerCardEditLinkOpenerCallback = callback;
    }
}