chromium/chrome/android/java/src/org/chromium/chrome/browser/autofill/vcn/AutofillVcnEnrollBottomSheetBridge.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.vcn;

import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;

import androidx.annotation.VisibleForTesting;
import androidx.browser.customtabs.CustomTabsIntent;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.JniType;
import org.jni_zero.NativeMethods;

import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.chrome.browser.ChromeStringConstants;
import org.chromium.chrome.browser.autofill.AutofillUiUtils;
import org.chromium.chrome.browser.autofill.vcn.AutofillVcnEnrollBottomSheetProperties.Description;
import org.chromium.chrome.browser.autofill.vcn.AutofillVcnEnrollBottomSheetProperties.IssuerIcon;
import org.chromium.chrome.browser.autofill.vcn.AutofillVcnEnrollBottomSheetProperties.LegalMessages;
import org.chromium.chrome.browser.layouts.LayoutManagerProvider;
import org.chromium.chrome.browser.layouts.LayoutStateProvider;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorSupplier;
import org.chromium.components.autofill.VirtualCardEnrollmentLinkType;
import org.chromium.components.autofill.payments.LegalMessageLine;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modelutil.PropertyModel;

import java.util.LinkedList;

/** Bridge for the virtual card enrollment bottom sheet. */
@JNINamespace("autofill")
/*package*/ class AutofillVcnEnrollBottomSheetBridge
        implements AutofillVcnEnrollBottomSheetCoordinator.Delegate,
                AutofillVcnEnrollBottomSheetProperties.LinkOpener {
    private long mNativeAutofillVcnEnrollBottomSheetBridge;
    private Context mContext;
    private AutofillVcnEnrollBottomSheetCoordinator mCoordinator;

    private LayoutStateProvider mLayoutStateProviderForTesting;
    private ObservableSupplier<TabModelSelector> mTabModelSelectorSupplierForTesting;

    @CalledByNative
    @VisibleForTesting
    AutofillVcnEnrollBottomSheetBridge() {}

    /**
     * Requests to show the bottom sheet. Called via JNI from C++.
     *
     * @param nativeAutofillVcnEnrollBottomSheetBridge The native pointer to the C++ bridge that
     *     receives callbacks.
     * @param webContents The web contents where the bottom sheet should show.
     * @param messageText The prompt message for the bottom sheet, e.g., "Make it more secure with a
     *     virtual card next time?"
     * @param descriptionText A text that describes what a virtual card does, e.g., "A virtual card
     *     hides your actual card..." and so on. This text includes a "learn more" link text.
     * @param learnMoreLinkText The text of the "learn more" link in descriptionText.
     * @param cardContainerAccessibilityDescription The accessibility description for the UI element
     *     that contains the issuer icon, card label, and card description.
     * @param issuerIconBitmap The icon for the card. For example, could be an American Express
     *     logo.
     * @param cardLabel The label for the card, e.g., "Amex ****1234".
     * @param cardDescription The description of the card, e.g., "Virtual Card".
     * @param googleLegalMessages Legal messages from Google Pay.
     * @param issuerLegalMessages Legal messages from the issuer bank.
     * @param acceptButtonLabel The label for the button that enrolls a virtual card.
     * @param cancelButtonLabel The label for the button that cancels enrollment.
     * @param loadingDescription The description for the loading view.
     * @return True if shown.
     */
    @CalledByNative
    @VisibleForTesting
    boolean requestShowContent(
            long nativeAutofillVcnEnrollBottomSheetBridge,
            WebContents webContents,
            @JniType("std::u16string") String messageText,
            @JniType("std::u16string") String descriptionText,
            @JniType("std::u16string") String learnMoreLinkText,
            @JniType("std::u16string") String cardContainerAccessibilityDescription,
            Bitmap issuerIconBitmap,
            @JniType("std::u16string") String cardLabel,
            @JniType("std::u16string") String cardDescription,
            LinkedList<LegalMessageLine> googleLegalMessages,
            LinkedList<LegalMessageLine> issuerLegalMessages,
            @JniType("std::u16string") String acceptButtonLabel,
            @JniType("std::u16string") String cancelButtonLabel,
            @JniType("std::u16string") String loadingDescription) {
        if (webContents == null || webContents.isDestroyed()) return false;

        WindowAndroid window = webContents.getTopLevelNativeWindow();
        if (window == null) return false;

        mContext = window.getContext().get();
        if (mContext == null) return false;

        if (mNativeAutofillVcnEnrollBottomSheetBridge != 0) return false;
        mNativeAutofillVcnEnrollBottomSheetBridge = nativeAutofillVcnEnrollBottomSheetBridge;

        AutofillUiUtils.CardIconSpecs cardIconSpecs =
                AutofillUiUtils.CardIconSpecs.create(mContext, AutofillUiUtils.CardIconSize.LARGE);

        PropertyModel.Builder modelBuilder =
                new PropertyModel.Builder(AutofillVcnEnrollBottomSheetProperties.ALL_KEYS)
                        .with(AutofillVcnEnrollBottomSheetProperties.MESSAGE_TEXT, messageText)
                        .with(
                                AutofillVcnEnrollBottomSheetProperties.DESCRIPTION,
                                new Description(
                                        descriptionText,
                                        learnMoreLinkText,
                                        ChromeStringConstants
                                                .AUTOFILL_VIRTUAL_CARD_ENROLLMENT_SUPPORT_URL,
                                        VirtualCardEnrollmentLinkType
                                                .VIRTUAL_CARD_ENROLLMENT_LEARN_MORE_LINK,
                                        /* linkOpener= */ this))
                        .with(
                                AutofillVcnEnrollBottomSheetProperties
                                        .CARD_CONTAINER_ACCESSIBILITY_DESCRIPTION,
                                cardContainerAccessibilityDescription)
                        .with(
                                AutofillVcnEnrollBottomSheetProperties.ISSUER_ICON,
                                new IssuerIcon(
                                        issuerIconBitmap,
                                        cardIconSpecs.getWidth(),
                                        cardIconSpecs.getHeight()))
                        .with(AutofillVcnEnrollBottomSheetProperties.CARD_LABEL, cardLabel)
                        .with(
                                AutofillVcnEnrollBottomSheetProperties.CARD_DESCRIPTION,
                                cardDescription)
                        .with(
                                AutofillVcnEnrollBottomSheetProperties.GOOGLE_LEGAL_MESSAGES,
                                new LegalMessages(
                                        googleLegalMessages,
                                        VirtualCardEnrollmentLinkType
                                                .VIRTUAL_CARD_ENROLLMENT_GOOGLE_PAYMENTS_TOS_LINK,
                                        /* linkOpener= */ this))
                        .with(
                                AutofillVcnEnrollBottomSheetProperties.ISSUER_LEGAL_MESSAGES,
                                new LegalMessages(
                                        issuerLegalMessages,
                                        VirtualCardEnrollmentLinkType
                                                .VIRTUAL_CARD_ENROLLMENT_ISSUER_TOS_LINK,
                                        /* linkOpener= */ this))
                        .with(
                                AutofillVcnEnrollBottomSheetProperties.ACCEPT_BUTTON_LABEL,
                                acceptButtonLabel)
                        .with(
                                AutofillVcnEnrollBottomSheetProperties.CANCEL_BUTTON_LABEL,
                                cancelButtonLabel)
                        .with(AutofillVcnEnrollBottomSheetProperties.SHOW_LOADING_STATE, false)
                        .with(
                                AutofillVcnEnrollBottomSheetProperties.LOADING_DESCRIPTION,
                                loadingDescription);

        mCoordinator =
                new AutofillVcnEnrollBottomSheetCoordinator(
                        mContext,
                        modelBuilder,
                        mLayoutStateProviderForTesting != null
                                ? mLayoutStateProviderForTesting
                                : LayoutManagerProvider.from(window),
                        mTabModelSelectorSupplierForTesting != null
                                ? mTabModelSelectorSupplierForTesting
                                : TabModelSelectorSupplier.from(window),
                        /* delegate= */ this);

        return mCoordinator.requestShowContent(window);
    }

    void setLayoutStateProviderForTesting(LayoutStateProvider layoutStateProvider) {
        mLayoutStateProviderForTesting = layoutStateProvider;
    }

    void setTabModelSelectorSupplierForTesting(
            ObservableSupplier<TabModelSelector> tabModelSelectorSupplier) {
        mTabModelSelectorSupplierForTesting = tabModelSelectorSupplier;
    }

    // AutofillVcnEnrollBottomSheetProperties.LinkOpener:
    @Override
    public void openLink(String url, @VirtualCardEnrollmentLinkType int linkType) {
        new CustomTabsIntent.Builder()
                .setShowTitle(true)
                .build()
                .launchUrl(mContext, Uri.parse(url));

        if (mNativeAutofillVcnEnrollBottomSheetBridge == 0) return;
        AutofillVcnEnrollBottomSheetBridgeJni.get()
                .recordLinkClickMetric(mNativeAutofillVcnEnrollBottomSheetBridge, linkType);
    }

    // AutofillVcnEnrollBottomSheetCoordinator.Delegate:
    @Override
    public void onAccept() {
        if (mNativeAutofillVcnEnrollBottomSheetBridge == 0) return;
        AutofillVcnEnrollBottomSheetBridgeJni.get()
                .onAccept(mNativeAutofillVcnEnrollBottomSheetBridge);
        mNativeAutofillVcnEnrollBottomSheetBridge = 0;
    }

    // AutofillVcnEnrollBottomSheetCoordinator.Delegate:
    @Override
    public void onCancel() {
        if (mNativeAutofillVcnEnrollBottomSheetBridge == 0) return;
        AutofillVcnEnrollBottomSheetBridgeJni.get()
                .onCancel(mNativeAutofillVcnEnrollBottomSheetBridge);
        mNativeAutofillVcnEnrollBottomSheetBridge = 0;
    }

    // AutofillVcnEnrollBottomSheetCoordinator.Delegate:
    @Override
    public void onDismiss() {
        if (mNativeAutofillVcnEnrollBottomSheetBridge == 0) return;
        AutofillVcnEnrollBottomSheetBridgeJni.get()
                .onDismiss(mNativeAutofillVcnEnrollBottomSheetBridge);
        mNativeAutofillVcnEnrollBottomSheetBridge = 0;
    }

    @CalledByNative
    @VisibleForTesting
    void hide() {
        mNativeAutofillVcnEnrollBottomSheetBridge = 0;
        if (mCoordinator == null) return;
        mCoordinator.hide();
        mCoordinator = null;
    }

    AutofillVcnEnrollBottomSheetCoordinator getCoordinatorForTesting() {
        return mCoordinator;
    }

    @NativeMethods
    interface Natives {
        void onAccept(long nativeAutofillVCNEnrollBottomSheetBridge);

        void onCancel(long nativeAutofillVCNEnrollBottomSheetBridge);

        void onDismiss(long nativeAutofillVCNEnrollBottomSheetBridge);

        void recordLinkClickMetric(long nativeAutofillVCNEnrollBottomSheetBridge, int linkType);
    }
}