blob: b4cb670e94ba719618ff3e99a3f938e1b8e96ad3 [file] [log] [blame]
// 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;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.media.ThumbnailUtils;
import android.support.annotation.ColorInt;
import android.support.annotation.Nullable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.support.v4.text.TextUtilsCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.content.res.AppCompatResources;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.json.JSONObject;
import org.chromium.base.Callback;
import org.chromium.chrome.autofill_assistant.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
import org.chromium.chrome.browser.autofill_assistant.ui.BottomBarAnimations;
import org.chromium.chrome.browser.autofill_assistant.ui.TouchEventFilter;
import org.chromium.chrome.browser.cached_image_fetcher.CachedImageFetcher;
import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation;
import org.chromium.chrome.browser.help.HelpAndFeedback;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.snackbar.Snackbar;
import org.chromium.chrome.browser.snackbar.SnackbarManager;
import org.chromium.components.variations.VariationsAssociatedData;
import org.chromium.content_public.browser.WebContents;
import org.chromium.payments.mojom.PaymentOptions;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/** Delegate to interact with the assistant UI. */
class AutofillAssistantUiDelegate {
private static final String FEEDBACK_CATEGORY_TAG =
"com.android.chrome.USER_INITIATED_FEEDBACK_REPORT_AUTOFILL_ASSISTANT";
private static final int PROGRESS_BAR_INITIAL_PROGRESS = 10;
private static final int DETAILS_PULSING_DURATION_MS = 1_000;
// TODO(crbug.com/806868): Use correct user locale.
private static final SimpleDateFormat sDetailsTimeFormat =
new SimpleDateFormat("H:mma", Locale.getDefault());
private static final SimpleDateFormat sDetailsDateFormat =
new SimpleDateFormat("EEE, MMM d", Locale.getDefault());
private final ChromeActivity mActivity;
private final Client mClient;
private final ViewGroup mCoordinatorView;
private final View mFullContainer;
private final View mOverlay;
private final TouchEventFilter mTouchEventFilter;
private final LinearLayout mBottomBar;
private final HorizontalScrollView mCarouselScroll;
private final ViewGroup mChipsViewContainer;
private final TextView mStatusMessageView;
private final AnimatedProgressBar mProgressBar;
private final ViewGroup mDetailsViewContainer;
private final ImageView mDetailsImage;
private final TextView mDetailsTitle;
private final TextView mDetailsText;
private final int mDetailsImageWidth;
private final int mDetailsImageHeight;
private final BottomBarAnimations mBottomBarAnimations;
private final boolean mIsRightToLeftLayout;
private ValueAnimator mDetailsPulseAnimation = null;
@Nullable
private Details mDetails;
private String mStatusMessage;
private AutofillAssistantPaymentRequest mPaymentRequest;
/**
* This is a client interface that relays interactions from the UI.
*
* Java version of the native autofill_assistant::UiDelegate.
*/
public interface Client extends TouchEventFilter.Client {
/**
* Called when clicking on the overlay.
*/
void onClickOverlay();
/**
* Called when the bottom bar is dismissing.
*/
void onDismiss();
/**
* Called when a script has been selected.
*
* @param scriptPath The path for the selected script.
*/
void onScriptSelected(String scriptPath);
/**
* Called when an address has been selected.
*
* @param guid The GUID of the selected address.
*/
void onAddressSelected(String guid);
/**
* Called when a credit card has been selected.
*
* @param guid The GUID of the selected card.
*/
void onCardSelected(String guid);
/**
* Used to access information relevant for debugging purposes.
*
* @return A string describing the current execution context.
*/
String getDebugContext();
/**
* Called when the init was successful.
*/
void onInitOk();
}
/**
* Java side equivalent of autofill_assistant::ScriptHandle.
*/
protected static class ScriptHandle {
/** The display name of this script. */
private final String mName;
/** The script path. */
private final String mPath;
/** Whether the script should be highlighted. */
private final boolean mHighlight;
/** Constructor. */
public ScriptHandle(String name, String path, boolean highlight) {
mName = name;
mPath = path;
mHighlight = highlight;
}
/** Returns the display name. */
public String getName() {
return mName;
}
/** Returns the script path. */
public String getPath() {
return mPath;
}
/** Returns whether the script should be highlighted. */
public boolean isHighlight() {
return mHighlight;
}
}
/**
* Java side equivalent of autofill_assistant::DetailsProto.
*/
static class Details {
private final String mTitle;
private final String mUrl;
@Nullable
private final Date mDate;
private final String mDescription;
private final boolean mIsFinal;
public Details(String title, String url, @Nullable Date date, String description,
boolean isFinal) {
this.mTitle = title;
this.mUrl = url;
this.mDate = date;
this.mDescription = description;
this.mIsFinal = isFinal;
}
String getTitle() {
return mTitle;
}
String getUrl() {
return mUrl;
}
@Nullable
Date getDate() {
return mDate;
}
String getDescription() {
return mDescription;
}
JSONObject toJSONObject() {
// Details are part of the feedback form, hence they need a JSON representation.
Map<String, String> movieDetails = new HashMap<>();
movieDetails.put("title", mTitle);
movieDetails.put("url", mUrl);
if (mDate != null) movieDetails.put("date", mDate.toString());
movieDetails.put("description", mDescription);
return new JSONObject(movieDetails);
}
/**
* Whether the details are not subject to change anymore. If set to false the animated
* placeholders will be displayed in place of missing data.
*/
boolean isFinal() {
return mIsFinal;
}
boolean isEmpty() {
return mTitle.isEmpty() && mUrl.isEmpty() && mDescription.isEmpty() && mDate == null;
}
}
// Names borrowed from :
// - https://guidelines.googleplex.com/googlematerial/components/chips.html
// - https://guidelines.googleplex.com/googlematerial/components/buttons.html
private enum ChipStyle { CHIP_ASSISTIVE, BUTTON_FILLED, BUTTON_HAIRLINE }
/**
* Constructs an assistant UI delegate.
*
* @param activity The ChromeActivity
* @param client The client to forward events to
*/
public AutofillAssistantUiDelegate(ChromeActivity activity, Client client) {
mActivity = activity;
mClient = client;
mCoordinatorView = (ViewGroup) mActivity.findViewById(R.id.coordinator);
mFullContainer = LayoutInflater.from(mActivity)
.inflate(R.layout.autofill_assistant_sheet, mCoordinatorView)
.findViewById(R.id.autofill_assistant);
// TODO(crbug.com/806868): Set hint text on overlay.
mOverlay = mFullContainer.findViewById(R.id.overlay);
mOverlay.setOnClickListener(unusedView -> mClient.onClickOverlay());
mTouchEventFilter = (TouchEventFilter) mFullContainer.findViewById(R.id.touch_event_filter);
mTouchEventFilter.init(client, activity.getFullscreenManager());
mBottomBar = mFullContainer.findViewById(R.id.bottombar);
mBottomBar.findViewById(R.id.close_button)
.setOnClickListener(unusedView -> mClient.onDismiss());
mBottomBar.findViewById(R.id.feedback_button)
.setOnClickListener(unusedView
-> HelpAndFeedback.getInstance(mActivity).showFeedback(mActivity,
Profile.getLastUsedProfile(), mActivity.getActivityTab().getUrl(),
FEEDBACK_CATEGORY_TAG,
FeedbackContext.buildContextString(
mActivity, mClient, mDetails, mStatusMessage, 4)));
mCarouselScroll = mBottomBar.findViewById(R.id.carousel_scroll);
mChipsViewContainer = mCarouselScroll.findViewById(R.id.carousel);
mStatusMessageView = mBottomBar.findViewById(R.id.status_message);
mProgressBar = new AnimatedProgressBar(mBottomBar.findViewById(R.id.progress_bar),
mActivity.getColor(R.color.modern_blue_600),
mActivity.getColor(R.color.modern_blue_600_alpha_38_opaque));
mDetailsViewContainer = (ViewGroup) mBottomBar.findViewById(R.id.details);
mDetailsImage = mDetailsViewContainer.findViewById(R.id.details_image);
mDetailsTitle = (TextView) mDetailsViewContainer.findViewById(R.id.details_title);
mDetailsText = (TextView) mDetailsViewContainer.findViewById(R.id.details_text);
mDetailsImageWidth = mActivity.getResources().getDimensionPixelSize(
R.dimen.autofill_assistant_details_image_size);
mDetailsImageHeight = mActivity.getResources().getDimensionPixelSize(
R.dimen.autofill_assistant_details_image_size);
mBottomBarAnimations = new BottomBarAnimations(mBottomBar, mDetailsViewContainer,
mChipsViewContainer, mActivity.getResources().getDisplayMetrics());
mIsRightToLeftLayout = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault())
== ViewCompat.LAYOUT_DIRECTION_RTL;
// Finch experiment to adjust overlay color
String overlayColor = VariationsAssociatedData.getVariationParamValue(
"AutofillAssistantOverlay", "overlay_color");
if (!overlayColor.isEmpty()) {
try {
mOverlay.setBackgroundColor(Color.parseColor(overlayColor));
} catch (IllegalArgumentException exception) {
// ignore
}
}
// TODO(crbug.com/806868): Listen for contextual search shown so as to hide this UI.
}
/**
* Shows a message in the status bar.
*
* @param message Message to display.
*/
public void showStatusMessage(@Nullable String message) {
/// keep a copy of the most recent status message for feedback purposes
mStatusMessage = message;
show();
mStatusMessageView.setText(message);
}
/**
* Updates the list of scripts in the bar.
*
* @param scriptHandles List of scripts to show.
*/
public void updateScripts(ArrayList<ScriptHandle> scriptHandles) {
if (scriptHandles.isEmpty()) {
clearCarousel();
return;
}
boolean alignRight = hasHighlightedScript(scriptHandles);
ChipStyle nonHighlightStyle =
alignRight ? ChipStyle.BUTTON_HAIRLINE : ChipStyle.CHIP_ASSISTIVE;
ArrayList<View> childViews = new ArrayList<>();
for (int i = 0; i < scriptHandles.size(); i++) {
ScriptHandle scriptHandle = scriptHandles.get(i);
ChipStyle chipStyle =
scriptHandle.isHighlight() ? ChipStyle.BUTTON_FILLED : nonHighlightStyle;
TextView chipView = createChipView(scriptHandle.getName(), chipStyle);
chipView.setOnClickListener((unusedView) -> {
clearCarousel();
mClient.onScriptSelected(scriptHandle.getPath());
});
childViews.add(chipView);
}
setCarouselChildViews(childViews, alignRight);
}
private boolean hasHighlightedScript(ArrayList<ScriptHandle> scripts) {
for (int i = 0; i < scripts.size(); i++) {
if (scripts.get(i).isHighlight()) {
return true;
}
}
return false;
}
private void clearCarousel() {
setCarouselChildViews(Collections.emptyList(), /* alignRight= */ false);
}
private void setCarouselChildViews(List<View> children, boolean alignRight) {
// TODO(crbug.com/806868): Pull the carousel logic into its own MVC component.
// Reverse alignRight if we are in a RTL layout.
alignRight = mIsRightToLeftLayout ? !alignRight : alignRight;
// Replace children.
// TODO(crbug.com/806868): We might want to animate children change using fade in/out
// animations.
mChipsViewContainer.removeAllViews();
setCarouselAlignment(alignRight);
for (int i = 0; i < children.size(); i++) {
// Add children in reverse order if the chips are right aligned.
int j = alignRight ? children.size() - i - 1 : i;
View child = children.get(j);
if (i > 0) {
LinearLayout.LayoutParams layoutParams =
(LinearLayout.LayoutParams) child.getLayoutParams();
int leftMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16,
child.getContext().getResources().getDisplayMetrics());
layoutParams.setMargins(leftMargin, 0, 0, 0);
child.setLayoutParams(layoutParams);
}
mChipsViewContainer.addView(child);
}
if (children.isEmpty()) {
mBottomBarAnimations.hideCarousel();
} else {
// Make sure the Autofill Assistant is visible.
show();
mBottomBarAnimations.showCarousel();
}
}
private void setCarouselAlignment(boolean alignRight) {
// Set carousel scroll gravity.
ViewGroup.LayoutParams currentLayoutParams = mCarouselScroll.getLayoutParams();
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(currentLayoutParams);
layoutParams.gravity = alignRight ? Gravity.END : Gravity.START;
mCarouselScroll.setLayoutParams(layoutParams);
// Reset the scroll position.
mCarouselScroll.post(
() -> mCarouselScroll.fullScroll(alignRight ? View.FOCUS_RIGHT : View.FOCUS_LEFT));
}
private TextView createChipView(String text, ChipStyle style) {
int resId = -1;
switch (style) {
case CHIP_ASSISTIVE:
resId = R.layout.autofill_assistant_chip_assistive;
break;
case BUTTON_FILLED:
resId = R.layout.autofill_assistant_button_filled;
break;
case BUTTON_HAIRLINE:
resId = R.layout.autofill_assistant_button_hairline;
break;
}
TextView chipView = (TextView) (LayoutInflater.from(mActivity).inflate(
resId, mChipsViewContainer, false));
chipView.setText(text);
return chipView;
}
public void show() {
if (mFullContainer.getVisibility() != View.VISIBLE) {
mFullContainer.setVisibility(View.VISIBLE);
// Set the initial progress. It is OK to make multiple calls to this method as it will
// have an effect only on the first one.
mProgressBar.maybeIncreaseProgress(PROGRESS_BAR_INITIAL_PROGRESS);
}
}
public void hide() {
mFullContainer.setVisibility(View.GONE);
}
public void showAutofillAssistantStoppedSnackbar(
SnackbarManager.SnackbarController controller) {
int durationMs = SnackbarManager.DEFAULT_SNACKBAR_DURATION_MS;
Snackbar snackBar =
Snackbar.make(mActivity.getString(
R.string.autofill_assistant_stopped, durationMs / 1_000),
controller, Snackbar.TYPE_ACTION,
Snackbar.UMA_AUTOFILL_ASSISTANT_STOP_UNDO)
.setAction(mActivity.getString(R.string.undo), /* actionData= */ null);
snackBar.setDuration(durationMs);
mActivity.getSnackbarManager().showSnackbar(snackBar);
}
public void dismissSnackbar(SnackbarManager.SnackbarController controller) {
mActivity.getSnackbarManager().dismissSnackbars(controller);
}
/**
* Enters a simplified pre-shutdown state, with only a goodbye message - the current status
* message - and a close button.
*/
public void enterGracefulShutdownMode() {
// TODO(crbug.com/806868): Introduce a proper, separate shutdown dialog, with enough space
// to display longer messages. Setting the max lines and hiding the feedback button are
// hacks to give enough space to display long messages.
mStatusMessageView.setMaxLines(4);
mBottomBar.findViewById(R.id.feedback_button).setVisibility(View.GONE);
hideProgressBar();
hideDetails();
mBottomBarAnimations.hideCarousel();
}
/** Called to show overlay. */
public void showOverlay() {
mOverlay.setVisibility(View.VISIBLE);
}
/** Called to hide overlay. */
public void hideOverlay() {
mOverlay.setVisibility(View.GONE);
}
public boolean isOverlayVisible() {
return mOverlay.getVisibility() == View.VISIBLE;
}
public void hideDetails() {
mBottomBarAnimations.hideDetails();
}
/** Called to show a message in the status bar that autofill assistant is done. */
public void showGiveUpMessage() {
showStatusMessage(mActivity.getString(R.string.autofill_assistant_give_up));
}
/** Called to show contextual information. */
public void showDetails(Details details) {
// keep a copy of most recent movie details for feedback purposes
mDetails = details;
Drawable defaultImage = AppCompatResources.getDrawable(
mActivity, R.drawable.autofill_assistant_default_details);
updateDetailsAnimation(details, (GradientDrawable) defaultImage);
mDetailsTitle.setText(details.getTitle());
String detailsText = getDetailsText(details);
mDetailsText.setText(detailsText);
String url = details.getUrl();
if (!url.isEmpty()) {
// The URL is safe given because it comes from the knowledge graph and is hosted on
// Google servers.
CachedImageFetcher.getInstance().fetchImage(url, image -> {
if (image != null) {
mDetailsImage.setImageDrawable(getRoundedImage(image));
mDetailsImage.setVisibility(View.VISIBLE);
} else {
mDetailsImage.setVisibility(View.GONE);
}
});
} else {
mDetailsImage.setVisibility(View.GONE);
if (!details.isFinal()) {
mDetailsImage.setImageDrawable(defaultImage);
mDetailsImage.setVisibility(View.VISIBLE);
}
}
// Make sure the Autofill Assistant is visible.
show();
mBottomBarAnimations.showDetails();
}
private void updateDetailsAnimation(Details details, GradientDrawable defaultImage) {
if (details.isFinal()) {
if (mDetailsPulseAnimation != null) {
mDetailsPulseAnimation.cancel();
}
return;
} else {
@ColorInt
int startColor = mActivity.getColor(R.color.modern_grey_100);
@ColorInt
int endColor = mActivity.getColor(R.color.modern_grey_50);
mDetailsPulseAnimation = ValueAnimator.ofInt(startColor, endColor);
mDetailsPulseAnimation.setDuration(DETAILS_PULSING_DURATION_MS);
mDetailsPulseAnimation.setEvaluator(new ArgbEvaluator());
mDetailsPulseAnimation.setRepeatCount(ValueAnimator.INFINITE);
mDetailsPulseAnimation.setRepeatMode(ValueAnimator.REVERSE);
mDetailsPulseAnimation.setInterpolator(ChromeAnimation.getAccelerateInterpolator());
mDetailsPulseAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
mDetailsTitle.setBackgroundColor(Color.WHITE);
mDetailsText.setBackgroundColor(Color.WHITE);
}
});
mDetailsPulseAnimation.addUpdateListener(animation -> {
if (details.getTitle().isEmpty()) {
mDetailsTitle.setBackgroundColor((int) animation.getAnimatedValue());
}
if (getDetailsText(details).isEmpty()) {
mDetailsText.setBackgroundColor((int) animation.getAnimatedValue());
}
defaultImage.setColor((int) animation.getAnimatedValue());
});
mDetailsPulseAnimation.start();
}
}
private String getDetailsText(Details details) {
List<String> parts = new ArrayList<>();
Date date = details.getDate();
if (date != null) {
parts.add(sDetailsTimeFormat.format(date).toLowerCase(Locale.getDefault()));
parts.add(sDetailsDateFormat.format(date));
}
String description = details.getDescription();
if (description != null && !description.isEmpty()) {
parts.add(description);
}
// TODO(crbug.com/806868): Use a view instead of this dot text.
return TextUtils.join(" • ", parts);
}
private Drawable getRoundedImage(Bitmap bitmap) {
RoundedBitmapDrawable roundedBitmap = RoundedBitmapDrawableFactory.create(
mActivity.getResources(),
ThumbnailUtils.extractThumbnail(bitmap, mDetailsImageWidth, mDetailsImageHeight));
roundedBitmap.setCornerRadius(TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 4, mActivity.getResources().getDisplayMetrics()));
return roundedBitmap;
}
public void showProgressBar(int progress, String message) {
show();
mProgressBar.show();
mProgressBar.maybeIncreaseProgress(progress);
if (!message.isEmpty()) {
mStatusMessageView.setText(message);
}
}
public void hideProgressBar() {
mProgressBar.hide();
}
public void enableProgressBarPulsing() {
mProgressBar.enablePulsing();
}
public void disableProgressBarPulsing() {
mProgressBar.disablePulsing();
}
/**
* Show profiles in the bar.
*
* @param profiles List of profiles to show.
*/
public void showProfiles(ArrayList<AutofillProfile> profiles) {
if (profiles.isEmpty()) {
clearCarousel();
mClient.onAddressSelected("");
return;
}
ArrayList<View> childViews = new ArrayList<>();
for (int i = 0; i < profiles.size(); i++) {
AutofillProfile profile = profiles.get(i);
// TODO(crbug.com/806868): Show more information than the street.
TextView chipView =
createChipView(profile.getStreetAddress(), ChipStyle.CHIP_ASSISTIVE);
chipView.setOnClickListener((unusedView) -> {
clearCarousel();
mClient.onAddressSelected(profile.getGUID());
});
childViews.add(chipView);
}
setCarouselChildViews(childViews, /* alignRight= */ false);
}
/**
* Show credit cards in the bar.
*
* @param cards List of cards to show.
*/
public void showCards(ArrayList<CreditCard> cards) {
if (cards.isEmpty()) {
mClient.onCardSelected("");
return;
}
ArrayList<View> childViews = new ArrayList<>();
for (int i = 0; i < cards.size(); i++) {
CreditCard card = cards.get(i);
// TODO(crbug.com/806868): Show more information than the card number.
TextView chipView =
createChipView(card.getObfuscatedNumber(), ChipStyle.CHIP_ASSISTIVE);
chipView.setOnClickListener((unusedView) -> {
clearCarousel();
mClient.onCardSelected(card.getGUID());
});
childViews.add(chipView);
}
setCarouselChildViews(childViews, /* alignRight= */ false);
}
/**
* Starts the init screen unless it has been marked to be skipped.
*/
public void startOrSkipInitScreen() {
if (InitScreenController.skip()) {
mClient.onInitOk();
return;
}
showInitScreen(new InitScreenController(mClient));
}
/**
* Shows the init screen and launch the autofill assistant when it succeeds.
*/
public void showInitScreen(InitScreenController controller) {
View initView = LayoutInflater.from(mActivity)
.inflate(R.layout.init_screen, mCoordinatorView)
.findViewById(R.id.init_screen);
initView.findViewById(R.id.close_button)
.setOnClickListener(unusedView -> onInitClicked(controller, false, initView));
initView.findViewById(R.id.chip_init_ok)
.setOnClickListener(unusedView -> onInitClicked(controller, true, initView));
initView.findViewById(R.id.chip_init_not_ok)
.setOnClickListener(unusedView -> onInitClicked(controller, false, initView));
}
private void onInitClicked(InitScreenController controller, Boolean initOk, View initView) {
CheckBox checkBox = initView.findViewById(R.id.checkbox_dont_show_init_again);
controller.onInitFinished(initOk, checkBox.isChecked());
mCoordinatorView.removeView(initView);
}
/**
* Show the payment request UI.
*
* Show the UI and return the selected information via |callback| when done.
*
* @param webContents The webContents.
* @param paymentOptions Options to request payment information.
* @param title Unused title.
* @param supportedBasicCardNetworks Optional array of supported basic card networks.
* @param callback Callback to return selected info.
*/
public void showPaymentRequest(WebContents webContents, PaymentOptions paymentOptions,
String unusedTitle, String[] supportedBasicCardNetworks,
Callback<AutofillAssistantPaymentRequest.SelectedPaymentInformation> callback) {
assert mPaymentRequest == null;
mPaymentRequest = new AutofillAssistantPaymentRequest(
webContents, paymentOptions, unusedTitle, supportedBasicCardNetworks);
// Make sure we wrap content in the container.
mBottomBarAnimations.setBottomBarHeightToWrapContent();
mPaymentRequest.show(mCarouselScroll, callback);
}
/** Close and destroy the payment request UI. */
public void closePaymentRequest() {
mPaymentRequest.close();
mPaymentRequest = null;
mBottomBarAnimations.setBottomBarHeightToFixed();
}
}