Commit d6dd515c authored by Mathias Carlen's avatar Mathias Carlen Committed by Commit Bot

[Autofill Assistant] Fork more of the payment request UI.

Before this change we only had a fork of the payment request logic, not
the UI underneath. This makes customizing the UI tricky without exposing larger
parts of the Payment Request UI to Autofill Assisatnt.

This patch tries to fork the PaymentRequestUI in a non invasive way. This
includes the java classes and the corresponding xml. We accept the potential
of being broken by changes in payments/ to non-forked parts and will fix that
on the Autofill Assistant side as needed and merge back later if that makes
sense.

As an example of how to customize the UI on the Autofill Assistant side, this
patch includes a customized PaymentRequestBottomBar (java and xml). The buttons
need more styling, which will be done in an upcoming change.

R=gogerald@chromium.org, twellington@chromium.org

    another use case. It should go away once we refactor the existing API
    and share more code.

Binary-Size: Increase is temporary due to a payment request UI reuse for
Bug: 806868
Change-Id: Iec801cc7d667e39c58e3604c0f62ea8289e90a1e
Reviewed-on: https://chromium-review.googlesource.com/c/1316730
Commit-Queue: Mathias Carlen <mcarlen@chromium.org>
Reviewed-by: default avatarTheresa <twellington@chromium.org>
Reviewed-by: default avatarGanggui Tang <gogerald@chromium.org>
Cr-Commit-Position: refs/heads/master@{#606401}
parent 33205a78
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2018 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<!-- Autofill Assistant specific PaymentRequestUI dialog
Sits at the bottom of the screen like a Bottom Sheet.
-->
<org.chromium.chrome.browser.widget.BoundedLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/autofill_assistant_payment_request"
android:orientation="vertical"
android:gravity="center"
app:maxWidthLandscape="@dimen/payments_ui_max_dialog_width"
app:maxWidthPortrait="@dimen/payments_ui_max_dialog_width"
android:background="@android:color/white" >
<include layout="@layout/payment_request_header" />
<include layout="@layout/payment_request_spinny" />
<org.chromium.chrome.browser.widget.FadingEdgeScrollView
android:id="@+id/option_container"
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_weight="1"
android:visibility="gone" >
<LinearLayout
android:id="@+id/payment_container_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</org.chromium.chrome.browser.widget.FadingEdgeScrollView>
<include layout="@layout/autofill_assistant_payment_request_bottom_bar" />
</org.chromium.chrome.browser.widget.BoundedLinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2018 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<!-- Autofill Assistant specific bottom of the payment request UI. -->
<org.chromium.chrome.browser.autofill_assistant.ui.PaymentRequestBottomBar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bottom_bar"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="@dimen/editor_dialog_section_large_spacing"
android:background="@android:color/white"
android:gravity="end"
android:orientation="horizontal"
android:visibility="gone" >
<!-- TODO(crbug.com/806868): Make the autofill assistant chip layouts into
styles and use those for these buttons.
-->
<org.chromium.ui.widget.ButtonCompat
android:id="@+id/button_secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="36dp"
android:text="@string/payments_edit_button"
style="@style/TextButton" />
<org.chromium.ui.widget.ButtonCompat
android:id="@+id/button_primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="36dp"
android:text="@string/payments_pay_button"
style="@style/FilledButton.Flat" />
</org.chromium.chrome.browser.autofill_assistant.ui.PaymentRequestBottomBar>
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
package org.chromium.chrome.browser.payments; package org.chromium.chrome.browser.autofill_assistant;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.os.Handler;
...@@ -15,13 +15,21 @@ import org.chromium.chrome.browser.ChromeActivity; ...@@ -15,13 +15,21 @@ import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.autofill.PersonalDataManager; import org.chromium.chrome.browser.autofill.PersonalDataManager;
import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile; import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard; import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
import org.chromium.chrome.browser.favicon.FaviconHelper; import org.chromium.chrome.browser.autofill_assistant.ui.PaymentRequestUI;
import org.chromium.chrome.browser.payments.AddressEditor;
import org.chromium.chrome.browser.payments.AutofillAddress;
import org.chromium.chrome.browser.payments.AutofillContact;
import org.chromium.chrome.browser.payments.AutofillPaymentApp;
import org.chromium.chrome.browser.payments.AutofillPaymentInstrument;
import org.chromium.chrome.browser.payments.BasicCardUtils;
import org.chromium.chrome.browser.payments.CardEditor;
import org.chromium.chrome.browser.payments.ContactEditor;
import org.chromium.chrome.browser.payments.ShippingStrings;
import org.chromium.chrome.browser.payments.ui.ContactDetailsSection; import org.chromium.chrome.browser.payments.ui.ContactDetailsSection;
import org.chromium.chrome.browser.payments.ui.PaymentInformation; import org.chromium.chrome.browser.payments.ui.PaymentInformation;
import org.chromium.chrome.browser.payments.ui.PaymentRequestUI; import org.chromium.chrome.browser.payments.ui.PaymentRequestUI.Client;
import org.chromium.chrome.browser.payments.ui.SectionInformation; import org.chromium.chrome.browser.payments.ui.SectionInformation;
import org.chromium.chrome.browser.payments.ui.ShoppingCart; import org.chromium.chrome.browser.payments.ui.ShoppingCart;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.ssl.SecurityStateModel; import org.chromium.chrome.browser.ssl.SecurityStateModel;
import org.chromium.chrome.browser.widget.prefeditor.Completable; import org.chromium.chrome.browser.widget.prefeditor.Completable;
import org.chromium.chrome.browser.widget.prefeditor.EditableOption; import org.chromium.chrome.browser.widget.prefeditor.EditableOption;
...@@ -43,7 +51,7 @@ import java.util.Map; ...@@ -43,7 +51,7 @@ import java.util.Map;
* TODO(crbug.com/806868): Refactor shared codes with PaymentRequestImpl to a common place when the * TODO(crbug.com/806868): Refactor shared codes with PaymentRequestImpl to a common place when the
* UX is fixed. * UX is fixed.
*/ */
public class AutofillAssistantPaymentRequest implements PaymentRequestUI.Client { public class AutofillAssistantPaymentRequest implements Client {
private static final String BASIC_CARD_PAYMENT_METHOD = "basic-card"; private static final String BASIC_CARD_PAYMENT_METHOD = "basic-card";
private static final Comparator<Completable> COMPLETENESS_COMPARATOR = private static final Comparator<Completable> COMPLETENESS_COMPARATOR =
(a, b) -> (b.isComplete() ? 1 : 0) - (a.isComplete() ? 1 : 0); (a, b) -> (b.isComplete() ? 1 : 0) - (a.isComplete() ? 1 : 0);
...@@ -189,15 +197,6 @@ public class AutofillAssistantPaymentRequest implements PaymentRequestUI.Client ...@@ -189,15 +197,6 @@ public class AutofillAssistantPaymentRequest implements PaymentRequestUI.Client
// 'Confirm'. // 'Confirm'.
mUI.updatePayButtonText(R.string.autofill_assistant_payment_info_confirm); mUI.updatePayButtonText(R.string.autofill_assistant_payment_info_confirm);
final FaviconHelper faviconHelper = new FaviconHelper();
faviconHelper.getLocalFaviconImageForURL(Profile.getLastUsedProfile(),
mWebContents.getLastCommittedUrl(),
activity.getResources().getDimensionPixelSize(R.dimen.payments_favicon_size),
(bitmap, iconUrl) -> {
if (mUI != null && bitmap != null) mUI.setTitleBitmap(bitmap);
faviconHelper.destroy();
});
mAddressEditor.setEditorDialog(mUI.getEditorDialog()); mAddressEditor.setEditorDialog(mUI.getEditorDialog());
mCardEditor.setEditorDialog(mUI.getCardEditorDialog()); mCardEditor.setEditorDialog(mUI.getCardEditorDialog());
if (mContactEditor != null) mContactEditor.setEditorDialog(mUI.getEditorDialog()); if (mContactEditor != null) mContactEditor.setEditorDialog(mUI.getEditorDialog());
......
...@@ -15,7 +15,6 @@ import org.chromium.base.annotations.CalledByNative; ...@@ -15,7 +15,6 @@ import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.browser.autofill.PersonalDataManager; import org.chromium.chrome.browser.autofill.PersonalDataManager;
import org.chromium.chrome.browser.customtabs.CustomTabActivity; import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.payments.AutofillAssistantPaymentRequest;
import org.chromium.chrome.browser.preferences.autofill_assistant.AutofillAssistantPreferences; import org.chromium.chrome.browser.preferences.autofill_assistant.AutofillAssistantPreferences;
import org.chromium.chrome.browser.snackbar.SnackbarManager; import org.chromium.chrome.browser.snackbar.SnackbarManager;
import org.chromium.chrome.browser.tab.EmptyTabObserver; import org.chromium.chrome.browser.tab.EmptyTabObserver;
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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_assistant.ui;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import org.chromium.chrome.autofill_assistant.R;
/** Autofill Assistant specific bottom bar for the payment request UI. */
public class PaymentRequestBottomBar extends LinearLayout {
private View mPrimaryButton;
private View mSecondaryButton;
/** Constructor for when the PaymentRequestBottomBar is inflated from XML. */
public PaymentRequestBottomBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mPrimaryButton = findViewById(R.id.button_primary);
mSecondaryButton = findViewById(R.id.button_secondary);
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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_assistant.ui;
import static org.chromium.chrome.browser.payments.ui.PaymentRequestSection.EDIT_BUTTON_GONE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.support.annotation.IntDef;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.animation.LinearOutSlowInInterpolator;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.Callback;
import org.chromium.chrome.autofill_assistant.R;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeVersionInfo;
import org.chromium.chrome.browser.payments.ShippingStrings;
import org.chromium.chrome.browser.payments.ui.DimmingDialog;
import org.chromium.chrome.browser.payments.ui.PaymentInformation;
import org.chromium.chrome.browser.payments.ui.PaymentRequestHeader;
import org.chromium.chrome.browser.payments.ui.PaymentRequestSection;
import org.chromium.chrome.browser.payments.ui.PaymentRequestSection.LineItemBreakdownSection;
import org.chromium.chrome.browser.payments.ui.PaymentRequestSection.OptionSection;
import org.chromium.chrome.browser.payments.ui.PaymentRequestSection.SectionSeparator;
import org.chromium.chrome.browser.payments.ui.PaymentRequestUI.Client;
import org.chromium.chrome.browser.payments.ui.PaymentRequestUiErrorView;
import org.chromium.chrome.browser.payments.ui.SectionInformation;
import org.chromium.chrome.browser.payments.ui.ShoppingCart;
import org.chromium.chrome.browser.widget.FadingEdgeScrollView;
import org.chromium.chrome.browser.widget.animation.FocusAnimator;
import org.chromium.chrome.browser.widget.prefeditor.EditableOption;
import org.chromium.chrome.browser.widget.prefeditor.EditorDialog;
import org.chromium.components.signin.ChromeSigninController;
import org.chromium.ui.text.NoUnderlineClickableSpan;
import org.chromium.ui.text.SpanApplier;
import org.chromium.ui.text.SpanApplier.SpanInfo;
import org.chromium.ui.widget.TextViewWithClickableSpans;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
/**
* The PaymentRequest UI.
*
* Note: This is an Autofill Assistant specific fork of payments/ui/PaymentRequestUI.java.
*/
public class PaymentRequestUI implements DialogInterface.OnDismissListener, View.OnClickListener,
PaymentRequestSection.SectionDelegate {
@IntDef({DataType.SHIPPING_ADDRESSES, DataType.SHIPPING_OPTIONS, DataType.CONTACT_DETAILS,
DataType.PAYMENT_METHODS})
@Retention(RetentionPolicy.SOURCE)
public @interface DataType {
int SHIPPING_ADDRESSES = 1;
int SHIPPING_OPTIONS = 2;
int CONTACT_DETAILS = 3;
int PAYMENT_METHODS = 4;
}
@IntDef({SelectionResult.ASYNCHRONOUS_VALIDATION, SelectionResult.EDITOR_LAUNCH,
SelectionResult.NONE})
@Retention(RetentionPolicy.SOURCE)
public @interface SelectionResult {
int ASYNCHRONOUS_VALIDATION = 1;
int EDITOR_LAUNCH = 2;
int NONE = 3;
}
/**
* Length of the animation to either show the UI or expand it to full height.
* Note that click of 'Pay' button is not accepted until the animation is done, so this duration
* also serves the function of preventing the user from accidently double-clicking on the screen
* when triggering payment and thus authorizing unwanted transaction.
*/
private static final int DIALOG_ENTER_ANIMATION_MS = 225;
/** Length of the animation to hide the bottom sheet UI. */
private static final int DIALOG_EXIT_ANIMATION_MS = 195;
private final Context mContext;
private final Client mClient;
private final boolean mRequestShipping;
private final boolean mRequestShippingOption;
private final boolean mRequestContactDetails;
private final boolean mShowDataSource;
private final DimmingDialog mDialog;
private final EditorDialog mEditorDialog;
private final EditorDialog mCardEditorDialog;
private final ViewGroup mRequestView;
private final PaymentRequestUiErrorView mErrorView;
private final Callback<PaymentInformation> mUpdateSectionsCallback;
private final ShippingStrings mShippingStrings;
private FadingEdgeScrollView mPaymentContainer;
private LinearLayout mPaymentContainerLayout;
private ViewGroup mBottomBar;
private Button mEditButton;
private Button mPayButton;
private View mCloseButton;
private View mSpinnyLayout;
private LineItemBreakdownSection mOrderSummarySection;
private OptionSection mShippingAddressSection;
private OptionSection mShippingOptionSection;
private OptionSection mContactDetailsSection;
private OptionSection mPaymentMethodSection;
private List<SectionSeparator> mSectionSeparators;
private PaymentRequestSection mSelectedSection;
private boolean mIsExpandedToFullHeight;
private boolean mIsProcessingPayClicked;
private boolean mIsClientClosing;
private boolean mIsClientCheckingSelection;
private boolean mIsShowingSpinner;
private boolean mIsEditingPaymentItem;
private boolean mIsClosing;
private SectionInformation mPaymentMethodSectionInformation;
private SectionInformation mShippingAddressSectionInformation;
private SectionInformation mShippingOptionsSectionInformation;
private SectionInformation mContactDetailsSectionInformation;
private Animator mSheetAnimator;
private FocusAnimator mSectionAnimator;
private int mAnimatorTranslation;
/**
* Builds the UI for PaymentRequest.
*
* @param activity The activity on top of which the UI should be displayed.
* @param client The consumer of the PaymentRequest UI.
* @param requestShipping Whether the UI should show the shipping address selection.
* @param requestShippingOption Whether the UI should show the shipping option selection.
* @param requestContact Whether the UI should show the payer name, email address and
* phone number selection.
* @param canAddCards Whether the UI should show the [+ADD CARD] button. This can be
* false, for example, when the merchant does not accept credit
* cards, so there's no point in adding cards within PaymentRequest
* UI.
* @param showDataSource Whether the UI should describe the source of Autofill data.
* @param title The title to show at the top of the UI. This can be, for
* example, the &lt;title&gt; of the merchant website. If the
* string is too long for UI, it elides at the end.
* @param origin The origin (https://tools.ietf.org/html/rfc6454) to show under
* the title. For example, "https://shop.momandpop.com". If the
* origin is too long for the UI, it should elide according to:
* https://www.chromium.org/Home/chromium-security/enamel#TOC-Eliding-Origin-Names-And-Hostnames
* @param securityLevel The security level of the page that invoked PaymentRequest.
* @param shippingStrings The string resource identifiers to use in the shipping sections.
*/
public PaymentRequestUI(Activity activity, Client client, boolean requestShipping,
boolean requestShippingOption, boolean requestContact, boolean canAddCards,
boolean showDataSource, String title, String origin, int securityLevel,
ShippingStrings shippingStrings) {
mContext = activity;
mClient = client;
mRequestShipping = requestShipping;
mRequestShippingOption = requestShippingOption;
mRequestContactDetails = requestContact;
mShowDataSource = showDataSource;
mAnimatorTranslation =
mContext.getResources().getDimensionPixelSize(R.dimen.payments_ui_translation);
mErrorView = (PaymentRequestUiErrorView) LayoutInflater.from(mContext).inflate(
R.layout.payment_request_error, null);
mErrorView.initialize(title, origin, securityLevel);
// This callback will be fired if mIsClientCheckingSelection is true.
mUpdateSectionsCallback = new Callback<PaymentInformation>() {
@Override
public void onResult(PaymentInformation result) {
mIsClientCheckingSelection = false;
updateOrderSummarySection(result.getShoppingCart());
if (mRequestShipping) {
updateSection(DataType.SHIPPING_ADDRESSES, result.getShippingAddresses());
}
if (mRequestShippingOption) {
updateSection(DataType.SHIPPING_OPTIONS, result.getShippingOptions());
}
if (mRequestContactDetails) {
updateSection(DataType.CONTACT_DETAILS, result.getContactDetails());
}
updateSection(DataType.PAYMENT_METHODS, result.getPaymentMethods());
if (mShippingAddressSectionInformation.getSelectedItem() == null) {
expand(mShippingAddressSection);
} else {
expand(null);
}
updatePayButtonEnabled();
}
};
mShippingStrings = shippingStrings;
mRequestView = (ViewGroup) LayoutInflater.from(mContext).inflate(
R.layout.autofill_assistant_payment_request, null);
prepareRequestView(mContext, title, origin, securityLevel, canAddCards);
mEditorDialog = new EditorDialog(activity, null,
/*deleteRunnable =*/null);
DimmingDialog.setVisibleStatusBarIconColor(mEditorDialog.getWindow());
mCardEditorDialog = new EditorDialog(activity, null,
/*deleteRunnable =*/null);
DimmingDialog.setVisibleStatusBarIconColor(mCardEditorDialog.getWindow());
// Allow screenshots of the credit card number in Canary, Dev, and developer builds.
if (ChromeVersionInfo.isBetaBuild() || ChromeVersionInfo.isStableBuild()) {
mCardEditorDialog.disableScreenshots();
}
mDialog = new DimmingDialog(activity, this);
}
/**
* Shows the PaymentRequest UI. This will dim the background behind the PaymentRequest UI.
*/
public void show() {
mDialog.addBottomSheetView(mRequestView);
mDialog.show();
mClient.getDefaultPaymentInformation(new Callback<PaymentInformation>() {
@Override
public void onResult(PaymentInformation result) {
updateOrderSummarySection(result.getShoppingCart());
if (mRequestShipping) {
updateSection(DataType.SHIPPING_ADDRESSES, result.getShippingAddresses());
}
if (mRequestShippingOption) {
updateSection(DataType.SHIPPING_OPTIONS, result.getShippingOptions());
}
if (mRequestContactDetails) {
updateSection(DataType.CONTACT_DETAILS, result.getContactDetails());
}
mPaymentMethodSection.setDisplaySummaryInSingleLineInNormalMode(
result.getPaymentMethods()
.getDisplaySelectedItemSummaryInSingleLineInNormalMode());
updateSection(DataType.PAYMENT_METHODS, result.getPaymentMethods());
updatePayButtonEnabled();
// Hide the loading indicators and show the real sections.
changeSpinnerVisibility(false);
mRequestView.addOnLayoutChangeListener(new SheetEnlargingAnimator(false));
}
});
}
/**
* Prepares the PaymentRequestUI for initial display.
*
* TODO(dfalcantara): Ideally, everything related to the request and its views would just be put
* into its own class but that'll require yanking out a lot of this class.
*
* @param context The application context.
* @param title Title of the page.
* @param origin The RFC6454 origin of the page.
* @param securityLevel The security level of the page that invoked PaymentRequest.
* @param canAddCards Whether new cards can be added.
*/
private void prepareRequestView(
Context context, String title, String origin, int securityLevel, boolean canAddCards) {
mSpinnyLayout = mRequestView.findViewById(R.id.payment_request_spinny);
assert mSpinnyLayout.getVisibility() == View.VISIBLE;
mIsShowingSpinner = true;
// Indicate that we're preparing the dialog for display.
TextView messageView = (TextView) mRequestView.findViewById(R.id.message);
messageView.setText(R.string.payments_loading_message);
((PaymentRequestHeader) mRequestView.findViewById(R.id.header))
.setTitleAndOrigin(title, origin, securityLevel);
// Set up the buttons.
mCloseButton = mRequestView.findViewById(R.id.close_button);
mCloseButton.setOnClickListener(this);
mBottomBar = (ViewGroup) mRequestView.findViewById(R.id.bottom_bar);
mPayButton = (Button) mBottomBar.findViewById(R.id.button_primary);
mPayButton.setOnClickListener(this);
mEditButton = (Button) mBottomBar.findViewById(R.id.button_secondary);
mEditButton.setOnClickListener(this);
// Create all the possible sections.
mSectionSeparators = new ArrayList<>();
mPaymentContainer = (FadingEdgeScrollView) mRequestView.findViewById(R.id.option_container);
mPaymentContainerLayout =
(LinearLayout) mRequestView.findViewById(R.id.payment_container_layout);
mOrderSummarySection = new LineItemBreakdownSection(context,
context.getString(R.string.payments_order_summary_label), this,
context.getString(R.string.payments_updated_label));
mShippingAddressSection = new OptionSection(
context, context.getString(mShippingStrings.getAddressLabel()), this);
mShippingOptionSection = new OptionSection(
context, context.getString(mShippingStrings.getOptionLabel()), this);
mContactDetailsSection = new OptionSection(
context, context.getString(R.string.payments_contact_details_label), this);
mPaymentMethodSection = new OptionSection(
context, context.getString(R.string.payments_method_of_payment_label), this);
// Display the summary of the selected address in multiple lines on bottom sheet.
mShippingAddressSection.setDisplaySummaryInSingleLineInNormalMode(false);
// Display selected shipping option name in the left summary text view and
// the cost in the right summary text view on bottom sheet.
mShippingOptionSection.setSplitSummaryInDisplayModeNormal(true);
// Some sections conditionally allow adding new options.
mShippingOptionSection.setCanAddItems(false);
mPaymentMethodSection.setCanAddItems(canAddCards);
// Put payment method section on top of address section for
// WEB_PAYMENTS_METHOD_SECTION_ORDER_V2.
boolean methodSectionOrderV2 =
ChromeFeatureList.isEnabled(ChromeFeatureList.WEB_PAYMENTS_METHOD_SECTION_ORDER_V2);
// Add the necessary sections to the layout.
mPaymentContainerLayout.addView(mOrderSummarySection,
new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
if (methodSectionOrderV2) {
mSectionSeparators.add(new SectionSeparator(mPaymentContainerLayout));
mPaymentContainerLayout.addView(mPaymentMethodSection,
new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
if (mRequestShipping) {
mSectionSeparators.add(new SectionSeparator(mPaymentContainerLayout));
// The shipping breakout sections are only added if they are needed.
mPaymentContainerLayout.addView(mShippingAddressSection,
new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
if (!methodSectionOrderV2) {
mSectionSeparators.add(new SectionSeparator(mPaymentContainerLayout));
mPaymentContainerLayout.addView(mPaymentMethodSection,
new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
if (mRequestContactDetails) {
// Contact details are optional, depending on the merchant website.
mSectionSeparators.add(new SectionSeparator(mPaymentContainerLayout));
mPaymentContainerLayout.addView(mContactDetailsSection,
new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
mRequestView.addOnLayoutChangeListener(new PeekingAnimator());
// Enabled in updatePayButtonEnabled() when the user has selected all payment options.
mPayButton.setEnabled(false);
}
/**
* Closes the UI. Can be invoked in response to, for example:
* <ul>
* <li>Successfully processing the payment.</li>
* <li>Failure to process the payment.</li>
* <li>The JavaScript calling the abort() method in PaymentRequest API.</li>
* <li>The PaymentRequest JavaScript object being destroyed.</li>
* </ul>
*
* Does not call Client.onDismissed().
*
* Should not be called multiple times.
*
* @param shouldCloseImmediately If true, this function will immediately dismiss the dialog
* without describing the error.
* @param callback The callback to notify of finished animations.
*/
public void close(boolean shouldCloseImmediately, final Runnable callback) {
mIsClientClosing = true;
Runnable dismissRunnable = new Runnable() {
@Override
public void run() {
dismissDialog(false);
if (callback != null) callback.run();
}
};
if (shouldCloseImmediately) {
// The shouldCloseImmediately boolean is true when the merchant calls
// instrumentResponse.complete("success") or instrumentResponse.complete("")
// in JavaScript.
dismissRunnable.run();
} else {
// Show the error dialog.
mDialog.showOverlay(mErrorView);
mErrorView.setDismissRunnable(dismissRunnable);
}
}
/**
* Update default text on the pay button to the given text.
*
* @param textResId The resource id of the text to be shown on the button.
*/
public void updatePayButtonText(int textResId) {
mPayButton.setText(textResId);
}
/**
* Updates the line items in response to a changed shipping address or option.
*
* @param cart The shopping cart, including the line items and the total.
*/
/* package */ void updateOrderSummarySection(ShoppingCart cart) {
if (cart == null || cart.getTotal() == null) {
mOrderSummarySection.setVisibility(View.GONE);
} else {
mOrderSummarySection.setVisibility(View.VISIBLE);
mOrderSummarySection.update(cart);
}
}
/**
* Updates the UI to account for changes in payment information.
*
* @param section The shipping options.
*/
public void updateSection(@DataType int whichSection, SectionInformation section) {
if (whichSection == DataType.SHIPPING_ADDRESSES) {
mShippingAddressSectionInformation = section;
mShippingAddressSection.update(section);
} else if (whichSection == DataType.SHIPPING_OPTIONS) {
mShippingOptionsSectionInformation = section;
mShippingOptionSection.update(section);
showShippingOptionSectionIfNecessary();
} else if (whichSection == DataType.CONTACT_DETAILS) {
mContactDetailsSectionInformation = section;
mContactDetailsSection.update(section);
} else if (whichSection == DataType.PAYMENT_METHODS) {
mPaymentMethodSectionInformation = section;
mPaymentMethodSection.update(section);
}
boolean isFinishingEditItem = mIsEditingPaymentItem;
mIsEditingPaymentItem = false;
updateSectionButtons();
updatePayButtonEnabled();
}
// Only show shipping option section once there are shipping options.
private void showShippingOptionSectionIfNecessary() {
if (!mRequestShippingOption || mShippingOptionsSectionInformation.isEmpty()
|| mPaymentContainerLayout.indexOfChild(mShippingOptionSection) != -1) {
return;
}
// Shipping option section is added below shipping address section.
int addressSectionIndex = mPaymentContainerLayout.indexOfChild(mShippingAddressSection);
SectionSeparator sectionSeparator =
new SectionSeparator(mPaymentContainerLayout, addressSectionIndex + 1);
mSectionSeparators.add(sectionSeparator);
if (mIsExpandedToFullHeight) sectionSeparator.expand();
mPaymentContainerLayout.addView(mShippingOptionSection, addressSectionIndex + 2,
new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
mPaymentContainerLayout.requestLayout();
}
@Override
public void onEditableOptionChanged(
final PaymentRequestSection section, EditableOption option) {
@SelectionResult
int result = SelectionResult.NONE;
if (section == mShippingAddressSection
&& mShippingAddressSectionInformation.getSelectedItem() != option) {
mShippingAddressSectionInformation.setSelectedItem(option);
result = mClient.onSectionOptionSelected(
DataType.SHIPPING_ADDRESSES, option, mUpdateSectionsCallback);
} else if (section == mShippingOptionSection
&& mShippingOptionsSectionInformation.getSelectedItem() != option) {
mShippingOptionsSectionInformation.setSelectedItem(option);
result = mClient.onSectionOptionSelected(
DataType.SHIPPING_OPTIONS, option, mUpdateSectionsCallback);
} else if (section == mContactDetailsSection) {
mContactDetailsSectionInformation.setSelectedItem(option);
result = mClient.onSectionOptionSelected(DataType.CONTACT_DETAILS, option, null);
} else if (section == mPaymentMethodSection) {
mPaymentMethodSectionInformation.setSelectedItem(option);
result = mClient.onSectionOptionSelected(DataType.PAYMENT_METHODS, option, null);
}
updateStateFromResult(section, result);
}
@Override
public void onEditEditableOption(final PaymentRequestSection section, EditableOption option) {
@SelectionResult
int result = SelectionResult.NONE;
assert section != mOrderSummarySection;
assert section != mShippingOptionSection;
if (section == mShippingAddressSection) {
assert mShippingAddressSectionInformation.getSelectedItem() == option;
result = mClient.onSectionEditOption(
DataType.SHIPPING_ADDRESSES, option, mUpdateSectionsCallback);
}
if (section == mContactDetailsSection) {
assert mContactDetailsSectionInformation.getSelectedItem() == option;
result = mClient.onSectionEditOption(DataType.CONTACT_DETAILS, option, null);
}
if (section == mPaymentMethodSection) {
assert mPaymentMethodSectionInformation.getSelectedItem() == option;
result = mClient.onSectionEditOption(DataType.PAYMENT_METHODS, option, null);
}
updateStateFromResult(section, result);
}
@Override
public void onAddEditableOption(PaymentRequestSection section) {
assert section != mShippingOptionSection;
@SelectionResult
int result = SelectionResult.NONE;
if (section == mShippingAddressSection) {
result = mClient.onSectionAddOption(
DataType.SHIPPING_ADDRESSES, mUpdateSectionsCallback);
} else if (section == mContactDetailsSection) {
result = mClient.onSectionAddOption(DataType.CONTACT_DETAILS, null);
} else if (section == mPaymentMethodSection) {
result = mClient.onSectionAddOption(DataType.PAYMENT_METHODS, null);
}
updateStateFromResult(section, result);
}
void updateStateFromResult(PaymentRequestSection section, @SelectionResult int result) {
mIsClientCheckingSelection = result == SelectionResult.ASYNCHRONOUS_VALIDATION;
mIsEditingPaymentItem = result == SelectionResult.EDITOR_LAUNCH;
if (mIsClientCheckingSelection) {
mSelectedSection = section;
updateSectionVisibility();
section.setDisplayMode(PaymentRequestSection.DISPLAY_MODE_CHECKING);
} else {
expand(null);
}
updatePayButtonEnabled();
}
@Override
public boolean isBoldLabelNeeded(PaymentRequestSection section) {
return section == mShippingAddressSection;
}
/** @return The common editor user interface. */
public EditorDialog getEditorDialog() {
return mEditorDialog;
}
/** @return The card editor user interface. Distinct from the common editor user interface,
* because the credit card editor can launch the address editor. */
public EditorDialog getCardEditorDialog() {
return mCardEditorDialog;
}
/**
* Called when user clicks anything in the dialog.
*/
@Override
public void onClick(View v) {
if (!isAcceptingCloseButton()) return;
if (v == mCloseButton) {
dismissDialog(true);
return;
}
if (!isAcceptingUserInput()) return;
// Users can only expand incomplete sections by clicking on their edit buttons.
if (v instanceof PaymentRequestSection) {
PaymentRequestSection section = (PaymentRequestSection) v;
if (section.getEditButtonState() != EDIT_BUTTON_GONE) return;
}
if (v == mOrderSummarySection) {
expand(mOrderSummarySection);
} else if (v == mShippingAddressSection) {
expand(mShippingAddressSection);
} else if (v == mShippingOptionSection) {
expand(mShippingOptionSection);
} else if (v == mContactDetailsSection) {
expand(mContactDetailsSection);
} else if (v == mPaymentMethodSection) {
expand(mPaymentMethodSection);
} else if (v == mPayButton) {
processPayButton();
} else if (v == mEditButton) {
if (mIsExpandedToFullHeight) {
dismissDialog(true);
} else {
expand(mOrderSummarySection);
}
}
updatePayButtonEnabled();
}
/**
* Dismiss the dialog.
*
* @param isAnimated If true, the dialog dismissal is animated.
*/
private void dismissDialog(boolean isAnimated) {
mIsClosing = true;
mDialog.dismiss(isAnimated);
}
private void processPayButton() {
assert !mIsShowingSpinner;
mIsProcessingPayClicked = true;
boolean shouldShowSpinner = mClient.onPayClicked(mShippingAddressSectionInformation == null
? null
: mShippingAddressSectionInformation.getSelectedItem(),
mShippingOptionsSectionInformation == null
? null
: mShippingOptionsSectionInformation.getSelectedItem(),
mPaymentMethodSectionInformation.getSelectedItem());
if (shouldShowSpinner) {
changeSpinnerVisibility(true);
} else {
mDialog.hide();
}
}
/**
* Called when user cancelled out of the UI that was shown after they clicked [PAY] button.
*/
public void onPayButtonProcessingCancelled() {
assert mIsProcessingPayClicked;
mIsProcessingPayClicked = false;
changeSpinnerVisibility(false);
mDialog.show();
updatePayButtonEnabled();
}
/**
* Called to show the processing message after instrument details have been loaded
* in the case the payment request UI has been skipped.
*/
public void showProcessingMessageAfterUiSkip() {
// Button was clicked before but not marked as clicked because we skipped the UI.
mIsProcessingPayClicked = true;
showProcessingMessage();
}
/**
* Called when the user has clicked on pay. The message is shown while the payment information
* is processed right until a confimation from the merchant is received.
*/
public void showProcessingMessage() {
assert mIsProcessingPayClicked;
changeSpinnerVisibility(true);
mDialog.show();
}
private void changeSpinnerVisibility(boolean showSpinner) {
if (mIsShowingSpinner == showSpinner) return;
mIsShowingSpinner = showSpinner;
if (showSpinner) {
mPaymentContainer.setVisibility(View.GONE);
mBottomBar.setVisibility(View.GONE);
mCloseButton.setVisibility(View.GONE);
mSpinnyLayout.setVisibility(View.VISIBLE);
// Turn the bottom sheet back into a collapsed bottom sheet showing only the spinner.
// TODO(dfalcantara): Animate this: https://crbug.com/621955
((FrameLayout.LayoutParams) mRequestView.getLayoutParams()).height =
LayoutParams.WRAP_CONTENT;
mRequestView.requestLayout();
} else {
mPaymentContainer.setVisibility(View.VISIBLE);
mBottomBar.setVisibility(View.VISIBLE);
mCloseButton.setVisibility(View.VISIBLE);
mSpinnyLayout.setVisibility(View.GONE);
if (mIsExpandedToFullHeight) {
((FrameLayout.LayoutParams) mRequestView.getLayoutParams()).height =
LayoutParams.MATCH_PARENT;
mRequestView.requestLayout();
}
}
}
private void updatePayButtonEnabled() {
boolean contactInfoOk = !mRequestContactDetails
|| (mContactDetailsSectionInformation != null
&& mContactDetailsSectionInformation.getSelectedItem() != null);
boolean shippingInfoOk = !mRequestShipping
|| (mShippingAddressSectionInformation != null
&& mShippingAddressSectionInformation.getSelectedItem() != null);
boolean shippingOptionInfoOk = !mRequestShippingOption
|| (mShippingOptionsSectionInformation != null
&& mShippingOptionsSectionInformation.getSelectedItem() != null);
mPayButton.setEnabled(contactInfoOk && shippingInfoOk && shippingOptionInfoOk
&& mPaymentMethodSectionInformation != null
&& mPaymentMethodSectionInformation.getSelectedItem() != null
&& !mIsClientCheckingSelection && !mIsEditingPaymentItem && !mIsClosing);
}
/** @return Whether or not the dialog can be closed via the X close button. */
private boolean isAcceptingCloseButton() {
return !mDialog.isAnimatingDisappearance() && mSheetAnimator == null
&& mSectionAnimator == null && !mIsProcessingPayClicked && !mIsEditingPaymentItem
&& !mIsClosing;
}
/** @return Whether or not the dialog is accepting user input. */
@Override
public boolean isAcceptingUserInput() {
return isAcceptingCloseButton() && mPaymentMethodSectionInformation != null
&& !mIsClientCheckingSelection;
}
/**
* Sets the observer to be called when the shipping address section gains or loses focus.
*
* @param observer The observer to notify.
*/
public void setShippingAddressSectionFocusChangedObserver(
OptionSection.FocusChangedObserver observer) {
mShippingAddressSection.setOptionSectionFocusChangedObserver(observer);
}
private void expand(PaymentRequestSection section) {
if (!mIsExpandedToFullHeight) {
// Container now takes the full height of the screen, animating towards it.
mRequestView.getLayoutParams().height = LayoutParams.MATCH_PARENT;
mRequestView.addOnLayoutChangeListener(new SheetEnlargingAnimator(true));
// New separators appear at the top and bottom of the list.
mPaymentContainer.setEdgeVisibility(
FadingEdgeScrollView.EdgeType.HARD, FadingEdgeScrollView.EdgeType.FADING);
mSectionSeparators.add(new SectionSeparator(mPaymentContainerLayout, -1));
// Add a link to Autofill settings.
addCardAndAddressOptionsSettingsView(mPaymentContainerLayout);
// Expand all the dividers.
for (int i = 0; i < mSectionSeparators.size(); i++) mSectionSeparators.get(i).expand();
mPaymentContainerLayout.requestLayout();
// Switch the 'edit' button to a 'cancel' button.
mEditButton.setText(mContext.getString(R.string.cancel));
// Disable all but the first button.
updateSectionButtons();
mIsExpandedToFullHeight = true;
}
// Update the section contents when they're selected.
mSelectedSection = section;
if (mSelectedSection == mOrderSummarySection) {
mClient.getShoppingCart(new Callback<ShoppingCart>() {
@Override
public void onResult(ShoppingCart result) {
updateOrderSummarySection(result);
updateSectionVisibility();
}
});
} else if (mSelectedSection == mShippingAddressSection) {
mClient.getSectionInformation(DataType.SHIPPING_ADDRESSES,
createUpdateSectionCallback(DataType.SHIPPING_ADDRESSES));
} else if (mSelectedSection == mShippingOptionSection) {
mClient.getSectionInformation(DataType.SHIPPING_OPTIONS,
createUpdateSectionCallback(DataType.SHIPPING_OPTIONS));
} else if (mSelectedSection == mContactDetailsSection) {
mClient.getSectionInformation(DataType.CONTACT_DETAILS,
createUpdateSectionCallback(DataType.CONTACT_DETAILS));
} else if (mSelectedSection == mPaymentMethodSection) {
mClient.getSectionInformation(DataType.PAYMENT_METHODS,
createUpdateSectionCallback(DataType.PAYMENT_METHODS));
} else {
updateSectionVisibility();
}
}
private void addCardAndAddressOptionsSettingsView(LinearLayout parent) {
String message;
if (!mShowDataSource) {
message = mContext.getString(R.string.payments_card_and_address_settings);
} else if (ChromeSigninController.get().isSignedIn()) {
message = mContext.getString(R.string.payments_card_and_address_settings_signed_in,
ChromeSigninController.get().getSignedInAccountName());
} else {
message = mContext.getString(R.string.payments_card_and_address_settings_signed_out);
}
NoUnderlineClickableSpan settingsSpan =
new NoUnderlineClickableSpan((widget) -> mClient.onCardAndAddressSettingsClicked());
SpannableString spannableMessage = SpanApplier.applySpans(
message, new SpanInfo("BEGIN_LINK", "END_LINK", settingsSpan));
TextView view = new TextViewWithClickableSpans(mContext);
view.setText(spannableMessage);
view.setMovementMethod(LinkMovementMethod.getInstance());
ApiCompatibilityUtils.setTextAppearance(view, R.style.BlackBody);
// Add paddings instead of margin to let getMeasuredHeight return correct value for section
// resize animation.
int paddingSize = mContext.getResources().getDimensionPixelSize(
R.dimen.editor_dialog_section_large_spacing);
ViewCompat.setPaddingRelative(view, paddingSize, paddingSize, paddingSize, paddingSize);
parent.addView(view);
}
private Callback<SectionInformation> createUpdateSectionCallback(@DataType final int type) {
return new Callback<SectionInformation>() {
@Override
public void onResult(SectionInformation result) {
updateSection(type, result);
updateSectionVisibility();
}
};
}
/** Update the display status of each expandable section in the full dialog. */
private void updateSectionVisibility() {
startSectionResizeAnimation();
mOrderSummarySection.focusSection(mSelectedSection == mOrderSummarySection);
mShippingAddressSection.focusSection(mSelectedSection == mShippingAddressSection);
mShippingOptionSection.focusSection(mSelectedSection == mShippingOptionSection);
mContactDetailsSection.focusSection(mSelectedSection == mContactDetailsSection);
mPaymentMethodSection.focusSection(mSelectedSection == mPaymentMethodSection);
updateSectionButtons();
}
/**
* Updates the enabled/disabled state of each section's edit button.
*
* Only the top-most button is enabled -- the others are disabled so the user is directed
* through the form from top to bottom.
*/
private void updateSectionButtons() {
// Disable edit buttons when the client is checking a selection.
boolean mayEnableButton = !mIsClientCheckingSelection;
for (int i = 0; i < mPaymentContainerLayout.getChildCount(); i++) {
View child = mPaymentContainerLayout.getChildAt(i);
if (!(child instanceof PaymentRequestSection)) continue;
PaymentRequestSection section = (PaymentRequestSection) child;
section.setIsEditButtonEnabled(mayEnableButton);
if (section.getEditButtonState() != EDIT_BUTTON_GONE) mayEnableButton = false;
}
}
/**
* Called when the dialog is dismissed. Can be caused by:
* <ul>
* <li>User click on the "back" button on the phone.</li>
* <li>User click on the "X" button in the top-right corner of the dialog.</li>
* <li>User click on the "CANCEL" button on the bottom of the dialog.</li>
* <li>Successfully processing the payment.</li>
* <li>Failure to process the payment.</li>
* <li>The JavaScript calling the abort() method in PaymentRequest API.</li>
* <li>The PaymentRequest JavaScript object being destroyed.</li>
* <li>User closing all incognito windows with PaymentRequest UI open in an incognito
* window.</li>
* </ul>
*/
@Override
public void onDismiss(DialogInterface dialog) {
mIsClosing = true;
if (mEditorDialog.isShowing()) mEditorDialog.dismiss();
if (mCardEditorDialog.isShowing()) mCardEditorDialog.dismiss();
if (!mIsClientClosing) mClient.onDismiss();
}
@Override
public String getAdditionalText(PaymentRequestSection section) {
if (section == mShippingAddressSection) {
int selectedItemIndex = mShippingAddressSectionInformation.getSelectedItemIndex();
if (selectedItemIndex != SectionInformation.NO_SELECTION
&& selectedItemIndex != SectionInformation.INVALID_SELECTION) {
return null;
}
String customErrorMessage = mShippingAddressSectionInformation.getErrorMessage();
if (selectedItemIndex == SectionInformation.INVALID_SELECTION
&& !TextUtils.isEmpty(customErrorMessage)) {
return customErrorMessage;
}
return mContext.getString(selectedItemIndex == SectionInformation.NO_SELECTION
? mShippingStrings.getSelectPrompt()
: mShippingStrings.getUnsupported());
} else if (section == mPaymentMethodSection) {
return mPaymentMethodSectionInformation.getAdditionalText();
} else {
return null;
}
}
@Override
public boolean isAdditionalTextDisplayingWarning(PaymentRequestSection section) {
return section == mShippingAddressSection && mShippingAddressSectionInformation != null
&& mShippingAddressSectionInformation.getSelectedItemIndex()
== SectionInformation.INVALID_SELECTION;
}
@Override
public void onSectionClicked(PaymentRequestSection section) {
expand(section);
}
/**
* Animates the different sections of the dialog expanding and contracting into their final
* positions.
*/
private void startSectionResizeAnimation() {
Runnable animationEndRunnable = new Runnable() {
@Override
public void run() {
mSectionAnimator = null;
}
};
mSectionAnimator =
new FocusAnimator(mPaymentContainerLayout, mSelectedSection, animationEndRunnable);
}
/**
* Animates the bottom sheet UI translating upwards from the bottom of the screen.
* Can be canceled when a {@link SheetEnlargingAnimator} starts and expands the dialog.
*/
private class PeekingAnimator
extends AnimatorListenerAdapter implements OnLayoutChangeListener {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
mRequestView.removeOnLayoutChangeListener(this);
mSheetAnimator = ObjectAnimator.ofFloat(
mRequestView, View.TRANSLATION_Y, mAnimatorTranslation, 0);
mSheetAnimator.setDuration(DIALOG_ENTER_ANIMATION_MS);
mSheetAnimator.setInterpolator(new LinearOutSlowInInterpolator());
mSheetAnimator.addListener(this);
mSheetAnimator.start();
}
@Override
public void onAnimationEnd(Animator animation) {
mSheetAnimator = null;
}
}
/** Animates the bottom sheet expanding to a larger sheet. */
private class SheetEnlargingAnimator
extends AnimatorListenerAdapter implements OnLayoutChangeListener {
private final boolean mIsBottomBarLockedInPlace;
private int mContainerHeightDifference;
public SheetEnlargingAnimator(boolean isBottomBarLockedInPlace) {
mIsBottomBarLockedInPlace = isBottomBarLockedInPlace;
}
/**
* Updates the animation.
*
* @param progress How far along the animation is. In the range [0,1], with 1 being done.
*/
private void update(float progress) {
// The dialog container initially starts off translated downward, gradually decreasing
// the translation until it is in the right place on screen.
float containerTranslation = mContainerHeightDifference * progress;
mRequestView.setTranslationY(containerTranslation);
if (mIsBottomBarLockedInPlace) {
// The bottom bar is translated along the dialog so that is looks like it stays in
// place at the bottom while the entire bottom sheet is translating upwards.
mBottomBar.setTranslationY(-containerTranslation);
// The payment container is sandwiched between the header and the bottom bar.
// Expansion animates by changing where its "bottom" is, letting its shadows appear
// and disappear as it changes size.
int paymentContainerBottom =
Math.min(mPaymentContainer.getTop() + mPaymentContainer.getMeasuredHeight(),
mBottomBar.getTop());
mPaymentContainer.setBottom(paymentContainerBottom);
}
}
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
if (mSheetAnimator != null) mSheetAnimator.cancel();
mRequestView.removeOnLayoutChangeListener(this);
mContainerHeightDifference = (bottom - top) - (oldBottom - oldTop);
ValueAnimator containerAnimator = ValueAnimator.ofFloat(1f, 0f);
containerAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float alpha = (Float) animation.getAnimatedValue();
update(alpha);
}
});
mSheetAnimator = containerAnimator;
mSheetAnimator.setDuration(DIALOG_ENTER_ANIMATION_MS);
mSheetAnimator.setInterpolator(new LinearOutSlowInInterpolator());
mSheetAnimator.addListener(this);
mSheetAnimator.start();
}
@Override
public void onAnimationEnd(Animator animation) {
// Reset the layout so that everything is in the expected place.
mRequestView.setTranslationY(0);
mBottomBar.setTranslationY(0);
mRequestView.requestLayout();
// Indicate that the dialog is ready to use.
mSheetAnimator = null;
}
}
}
...@@ -36,8 +36,12 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties; ...@@ -36,8 +36,12 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties;
* A fullscreen semitransparent dialog used for dimming Chrome when overlaying a bottom sheet * A fullscreen semitransparent dialog used for dimming Chrome when overlaying a bottom sheet
* dialog/CCT or an alert dialog on top of it. FLAG_DIM_BEHIND is not being used because it causes * dialog/CCT or an alert dialog on top of it. FLAG_DIM_BEHIND is not being used because it causes
* the web contents of a payment handler CCT to also dim on some versions of Android (e.g., Nougat). * the web contents of a payment handler CCT to also dim on some versions of Android (e.g., Nougat).
*
* Note: Do not use this class outside of the payments.ui package!
* TODO(crbug.com/806868): Revert the visibility to package default again when it is no longer used
* by Autofill Assistant.
*/ */
/* package */ class DimmingDialog { public class DimmingDialog {
/** /**
* Length of the animation to either show the UI or expand it to full height. Note that click of * Length of the animation to either show the UI or expand it to full height. Note that click of
* 'Pay' button in PaymentRequestUI is not accepted until the animation is done, so this * 'Pay' button in PaymentRequestUI is not accepted until the animation is done, so this
...@@ -60,8 +64,7 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties; ...@@ -60,8 +64,7 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties;
* @param activity The activity on top of which the dialog should be displayed. * @param activity The activity on top of which the dialog should be displayed.
* @param dismissListener The listener for the dismissal of this dialog. * @param dismissListener The listener for the dismissal of this dialog.
*/ */
/* package */ DimmingDialog( public DimmingDialog(Activity activity, DialogInterface.OnDismissListener dismissListener) {
Activity activity, DialogInterface.OnDismissListener dismissListener) {
// To handle the specced animations, the dialog is entirely contained within a translucent // To handle the specced animations, the dialog is entirely contained within a translucent
// FrameLayout. This could eventually be converted to a real BottomSheetDialog, but that // FrameLayout. This could eventually be converted to a real BottomSheetDialog, but that
// requires exploration of how interactions would work when the dialog can be sent back and // requires exploration of how interactions would work when the dialog can be sent back and
...@@ -87,14 +90,14 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties; ...@@ -87,14 +90,14 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties;
* Makes sure that the color of the icons in the status bar makes the icons visible. * Makes sure that the color of the icons in the status bar makes the icons visible.
* @param window The window whose status bar icon color is being set. * @param window The window whose status bar icon color is being set.
*/ */
/* package */ static void setVisibleStatusBarIconColor(Window window) { public static void setVisibleStatusBarIconColor(Window window) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
ApiCompatibilityUtils.setStatusBarIconColor(window.getDecorView().getRootView(), ApiCompatibilityUtils.setStatusBarIconColor(window.getDecorView().getRootView(),
!ColorUtils.shouldUseLightForegroundOnBackground(window.getStatusBarColor())); !ColorUtils.shouldUseLightForegroundOnBackground(window.getStatusBarColor()));
} }
/** @param bottomSheetView The view to show in the bottom sheet. */ /** @param bottomSheetView The view to show in the bottom sheet. */
/* package */ void addBottomSheetView(View bottomSheetView) { public void addBottomSheetView(View bottomSheetView) {
FrameLayout.LayoutParams bottomSheetParams = FrameLayout.LayoutParams bottomSheetParams =
new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
bottomSheetParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; bottomSheetParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
...@@ -103,12 +106,12 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties; ...@@ -103,12 +106,12 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties;
} }
/** Show the dialog. */ /** Show the dialog. */
/* package */ void show() { public void show() {
mDialog.show(); mDialog.show();
} }
/** Hide the dialog without dismissing it. */ /** Hide the dialog without dismissing it. */
/* package */ void hide() { public void hide() {
mDialog.hide(); mDialog.hide();
} }
...@@ -117,7 +120,7 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties; ...@@ -117,7 +120,7 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties;
* *
* @param isAnimated If true, the dialog dismissal is animated. * @param isAnimated If true, the dialog dismissal is animated.
*/ */
/* package */ void dismiss(boolean isAnimated) { public void dismiss(boolean isAnimated) {
if (!mDialog.isShowing()) return; if (!mDialog.isShowing()) return;
if (isAnimated) { if (isAnimated) {
new DisappearingAnimator(true); new DisappearingAnimator(true);
...@@ -127,7 +130,7 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties; ...@@ -127,7 +130,7 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties;
} }
/** @param overlay The overlay to show. This can be an error dialog, for example. */ /** @param overlay The overlay to show. This can be an error dialog, for example. */
/* package */ void showOverlay(View overlay) { public void showOverlay(View overlay) {
// Animate the bottom sheet going away. // Animate the bottom sheet going away.
new DisappearingAnimator(false); new DisappearingAnimator(false);
...@@ -140,7 +143,7 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties; ...@@ -140,7 +143,7 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties;
} }
/** @return Whether the dialog is currently animating disappearance. */ /** @return Whether the dialog is currently animating disappearance. */
/* package */ boolean isAnimatingDisappearance() { public boolean isAnimatingDisappearance() {
return mIsAnimatingDisappearance; return mIsAnimatingDisappearance;
} }
...@@ -190,7 +193,7 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties; ...@@ -190,7 +193,7 @@ import org.chromium.chrome.browser.widget.animation.AnimatorProperties;
private class DisappearingAnimator extends AnimatorListenerAdapter { private class DisappearingAnimator extends AnimatorListenerAdapter {
private final boolean mIsDialogClosing; private final boolean mIsDialogClosing;
/* package */ DisappearingAnimator(boolean removeDialog) { public DisappearingAnimator(boolean removeDialog) {
mIsDialogClosing = removeDialog; mIsDialogClosing = removeDialog;
View child = mFullContainer.getChildAt(0); View child = mFullContainer.getChildAt(0);
......
...@@ -127,7 +127,7 @@ public abstract class PaymentRequestSection extends LinearLayout implements View ...@@ -127,7 +127,7 @@ public abstract class PaymentRequestSection extends LinearLayout implements View
static final int DISPLAY_MODE_FOCUSED = 5; static final int DISPLAY_MODE_FOCUSED = 5;
/** Checking mode: Gray background, spinner overlay hides everything except the title. */ /** Checking mode: Gray background, spinner overlay hides everything except the title. */
static final int DISPLAY_MODE_CHECKING = 6; public static final int DISPLAY_MODE_CHECKING = 6;
protected final SectionDelegate mDelegate; protected final SectionDelegate mDelegate;
protected final int mLargeSpacing; protected final int mLargeSpacing;
......
...@@ -619,15 +619,6 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View ...@@ -619,15 +619,6 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View
mErrorView.setBitmap(bitmap); mErrorView.setBitmap(bitmap);
} }
/**
* Update default text on the pay button to the given text.
*
* @param textResId The resource id of the text to be shown on the button.
*/
public void updatePayButtonText(int textResId) {
mPayButton.setText(textResId);
}
/** /**
* Updates the line items in response to a changed shipping address or option. * Updates the line items in response to a changed shipping address or option.
* *
......
...@@ -118,9 +118,12 @@ chrome_java_sources = [ ...@@ -118,9 +118,12 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetCoordinator.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetCoordinator.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewBinder.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewBinder.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/AnimatedProgressBar.java", "java/src/org/chromium/chrome/browser/autofill_assistant/AnimatedProgressBar.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPaymentRequest.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java", "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiDelegate.java", "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiDelegate.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/InitScreenController.java", "java/src/org/chromium/chrome/browser/autofill_assistant/InitScreenController.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/ui/PaymentRequestBottomBar.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/ui/PaymentRequestUI.java",
"java/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTask.java", "java/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTask.java",
"java/src/org/chromium/chrome/browser/banners/AppBannerManager.java", "java/src/org/chromium/chrome/browser/banners/AppBannerManager.java",
"java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java", "java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java",
...@@ -1144,7 +1147,6 @@ chrome_java_sources = [ ...@@ -1144,7 +1147,6 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/payments/AutofillContact.java", "java/src/org/chromium/chrome/browser/payments/AutofillContact.java",
"java/src/org/chromium/chrome/browser/payments/AutofillPaymentApp.java", "java/src/org/chromium/chrome/browser/payments/AutofillPaymentApp.java",
"java/src/org/chromium/chrome/browser/payments/AutofillPaymentInstrument.java", "java/src/org/chromium/chrome/browser/payments/AutofillPaymentInstrument.java",
"java/src/org/chromium/chrome/browser/payments/AutofillAssistantPaymentRequest.java",
"java/src/org/chromium/chrome/browser/payments/BasicCardUtils.java", "java/src/org/chromium/chrome/browser/payments/BasicCardUtils.java",
"java/src/org/chromium/chrome/browser/payments/CanMakePaymentQuery.java", "java/src/org/chromium/chrome/browser/payments/CanMakePaymentQuery.java",
"java/src/org/chromium/chrome/browser/payments/CardEditor.java", "java/src/org/chromium/chrome/browser/payments/CardEditor.java",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment