// Copyright 2022 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.touch_to_fill.payments;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.chromium.base.ThreadUtils.runOnUiThreadBlocking;
import static org.chromium.base.test.util.CriteriaHelper.pollUiThread;
import static org.chromium.chrome.browser.autofill.AutofillTestHelper.createClickActionWithFlags;
import static org.chromium.chrome.browser.autofill.AutofillTestHelper.createCreditCard;
import static org.chromium.chrome.browser.autofill.AutofillTestHelper.createCreditCardSuggestion;
import static org.chromium.chrome.browser.autofill.AutofillTestHelper.createLocalCreditCard;
import static org.chromium.chrome.browser.autofill.AutofillTestHelper.createVirtualCreditCard;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.CreditCardSuggestionProperties.APPLY_DEACTIVATED_STYLE;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.CreditCardSuggestionProperties.FIRST_LINE_LABEL;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.CreditCardSuggestionProperties.ITEM_COLLECTION_INFO;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.CreditCardSuggestionProperties.MAIN_TEXT;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.CreditCardSuggestionProperties.MINOR_TEXT;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.CreditCardSuggestionProperties.NETWORK_NAME;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.CreditCardSuggestionProperties.NON_TRANSFORMING_CREDIT_CARD_SUGGESTION_KEYS;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.CreditCardSuggestionProperties.ON_CREDIT_CARD_CLICK_ACTION;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.DISMISS_HANDLER;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.IbanProperties.IBAN_NICKNAME;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.IbanProperties.IBAN_VALUE;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.IbanProperties.NON_TRANSFORMING_IBAN_KEYS;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.IbanProperties.ON_IBAN_CLICK_ACTION;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.CREDIT_CARD;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.FILL_BUTTON;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.IBAN;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.SHEET_ITEMS;
import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.VISIBLE;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.MediumTest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;
import org.chromium.base.Callback;
import org.chromium.base.ContextUtils;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.DoNotBatch;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.ScalableTimeout;
import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
import org.chromium.chrome.browser.autofill.PersonalDataManager.Iban;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.touch_to_fill.common.FillableItemCollectionInfo;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.components.autofill.AutofillSuggestion;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
import org.chromium.ui.accessibility.AccessibilityState;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/** Tests for {@link TouchToFillPaymentMethodView} */
@RunWith(ChromeJUnit4ClassRunner.class)
@DoNotBatch(reason = "The methods of ChromeAccessibilityUtil don't seem to work with batching.")
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class TouchToFillPaymentMethodViewTest {
private static final CreditCard VISA =
createCreditCard(
"Visa",
"4111111111111111",
"5",
"2050",
true,
"Visa",
"• • • • 1111",
0,
"visa");
private static final CreditCard NICKNAMED_VISA =
createCreditCard(
"Visa",
"4111111111111111",
"5",
"2050",
true,
"Best Card",
"• • • • 1111",
0,
"visa");
private static final CreditCard MASTERCARD =
createLocalCreditCard("MasterCard", "5555555555554444", "8", "2050");
private static final CreditCard VIRTUAL_CARD =
createVirtualCreditCard(
/* name= */ "Mojo Jojo",
/* number= */ "4111111111111111",
/* month= */ "4",
/* year= */ "2090",
/* network= */ "Visa",
/* iconId= */ 0,
/* cardNameForAutofillDisplay= */ "Visa",
/* obfuscatedLastFourDigits= */ "1111");
private static final CreditCard LONG_CARD_NAME_CARD =
createCreditCard(
"MJ",
"4111111111111111",
"5",
"2050",
false,
"How much wood would a woodchuck chuck if a woodchuck could chuck wood",
"• • • • 1111",
0,
"visa");
private static final AutofillSuggestion VISA_SUGGESTION =
createCreditCardSuggestion(
VISA.getCardNameForAutofillDisplay(),
VISA.getObfuscatedLastFourDigits(),
VISA.getFormattedExpirationDate(ContextUtils.getApplicationContext()),
/* applyDeactivatedStyle= */ false);
private static final AutofillSuggestion NICKNAMED_VISA_SUGGESTION =
createCreditCardSuggestion(
NICKNAMED_VISA.getCardNameForAutofillDisplay(),
NICKNAMED_VISA.getObfuscatedLastFourDigits(),
NICKNAMED_VISA.getFormattedExpirationDate(ContextUtils.getApplicationContext()),
/* applyDeactivatedStyle= */ false);
private static final AutofillSuggestion MASTERCARD_SUGGESTION =
createCreditCardSuggestion(
MASTERCARD.getName(),
MASTERCARD.getNumber(),
MASTERCARD.getFormattedExpirationDate(ContextUtils.getApplicationContext()),
/* applyDeactivatedStyle= */ false);
private static final AutofillSuggestion VIRTUAL_CARD_SUGGESTION =
createCreditCardSuggestion(
VIRTUAL_CARD.getCardNameForAutofillDisplay(),
VIRTUAL_CARD.getObfuscatedLastFourDigits(),
/* subLabel= */ "Virtual card",
/* applyDeactivatedStyle= */ false);
private static final AutofillSuggestion NON_ACCEPTABLE_VIRTUAL_CARD_SUGGESTION =
createCreditCardSuggestion(
VIRTUAL_CARD.getCardNameForAutofillDisplay(),
VIRTUAL_CARD.getObfuscatedLastFourDigits(),
/* subLabel= */ "Merchant doesn't accept this virtual card",
/* applyDeactivatedStyle= */ true);
private static final AutofillSuggestion LONG_CARD_NAME_CARD_SUGGESTION =
createCreditCardSuggestion(
LONG_CARD_NAME_CARD.getCardNameForAutofillDisplay(),
LONG_CARD_NAME_CARD.getObfuscatedLastFourDigits(),
LONG_CARD_NAME_CARD.getFormattedExpirationDate(
ContextUtils.getApplicationContext()),
/* applyDeactivatedStyle= */ false);
private static final Iban LOCAL_IBAN =
Iban.createLocal(
/* guid= */ "000000111111",
/* label= */ "CH56 **** **** **** *800 9",
/* nickname= */ "My brother's IBAN",
/* value= */ "CH5604835012345678009");
private static final Iban LOCAL_IBAN_NO_NICKNAME =
Iban.createLocal(
/* guid= */ "000000111111",
/* label= */ "CH56 **** **** **** *800 9",
/* nickname= */ "",
/* value= */ "CH5604835012345678009");
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
@Rule
public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
@Mock private Callback<Integer> mDismissCallback;
@Mock private FillableItemCollectionInfo mItemCollectionInfo;
private BottomSheetController mBottomSheetController;
private BottomSheetTestSupport mSheetTestSupport;
private TouchToFillPaymentMethodView mTouchToFillPaymentMethodView;
private PropertyModel mTouchToFillPaymentMethodModel;
@Before
public void setupTest() throws InterruptedException {
MockitoAnnotations.initMocks(this);
mActivityTestRule.startMainActivityOnBlankPage();
mBottomSheetController =
mActivityTestRule
.getActivity()
.getRootUiCoordinatorForTesting()
.getBottomSheetController();
mSheetTestSupport = new BottomSheetTestSupport(mBottomSheetController);
runOnUiThreadBlocking(
() -> {
mTouchToFillPaymentMethodModel =
new PropertyModel.Builder(TouchToFillPaymentMethodProperties.ALL_KEYS)
.with(VISIBLE, false)
.with(SHEET_ITEMS, new ModelList())
.with(DISMISS_HANDLER, mDismissCallback)
.build();
mTouchToFillPaymentMethodView =
new TouchToFillPaymentMethodView(
mActivityTestRule.getActivity(), mBottomSheetController);
PropertyModelChangeProcessor.create(
mTouchToFillPaymentMethodModel,
mTouchToFillPaymentMethodView,
TouchToFillPaymentMethodViewBinder::bindTouchToFillPaymentMethodView);
});
}
@Test
@MediumTest
public void testVisibilityChangedByModel() {
runOnUiThreadBlocking(
() -> {
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(
new ListItem(
CREDIT_CARD,
createCardSuggestionModel(
VISA, VISA_SUGGESTION, mItemCollectionInfo)));
});
// After setting the visibility to true, the view should exist and be visible.
runOnUiThreadBlocking(() -> mTouchToFillPaymentMethodModel.set(VISIBLE, true));
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
assertThat(mTouchToFillPaymentMethodView.getContentView().isShown(), is(true));
// After hiding the view, the view should still exist but be invisible.
runOnUiThreadBlocking(() -> mTouchToFillPaymentMethodModel.set(VISIBLE, false));
pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HIDDEN);
assertThat(mTouchToFillPaymentMethodView.getContentView().isShown(), is(false));
}
@Test
@MediumTest
public void testCredentialsChangedByModel() {
runOnUiThreadBlocking(
() -> {
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(
new ListItem(
CREDIT_CARD,
createCardSuggestionModel(
VISA, VISA_SUGGESTION, mItemCollectionInfo)));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(
new ListItem(
CREDIT_CARD,
createCardSuggestionModel(
MASTERCARD,
MASTERCARD_SUGGESTION,
mItemCollectionInfo)));
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(
new ListItem(
CREDIT_CARD,
createCardSuggestionModel(
VIRTUAL_CARD,
VIRTUAL_CARD_SUGGESTION,
mItemCollectionInfo)));
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
assertThat(getCreditCardSuggestions().getChildCount(), is(3));
assertThat(getSuggestionMainTextAt(0).getText(), is(VISA_SUGGESTION.getLabel()));
assertThat(getSuggestionMinorTextAt(0).getText(), is(VISA_SUGGESTION.getSecondaryLabel()));
assertThat(
getSuggestionLabelAt(0).getText(),
is(VISA.getFormattedExpirationDate(ContextUtils.getApplicationContext())));
assertThat(getSuggestionMainTextAt(1).getText(), is(MASTERCARD_SUGGESTION.getLabel()));
assertThat(
getSuggestionMinorTextAt(1).getText(),
is(MASTERCARD_SUGGESTION.getSecondaryLabel()));
assertThat(
getSuggestionLabelAt(1).getText(),
is(MASTERCARD.getFormattedExpirationDate(ContextUtils.getApplicationContext())));
assertThat(getSuggestionMainTextAt(2).getText(), is(VIRTUAL_CARD_SUGGESTION.getLabel()));
assertThat(
getSuggestionMinorTextAt(2).getText(),
is(VIRTUAL_CARD_SUGGESTION.getSecondaryLabel()));
assertThat(getSuggestionLabelAt(2).getText(), is(VIRTUAL_CARD_SUGGESTION.getSublabel()));
}
@Test
@MediumTest
public void testSheetStartsInFullHeightForAccessibility() {
// Enabling the accessibility settings.
runOnUiThreadBlocking(
() -> {
AccessibilityState.setIsTouchExplorationEnabledForTesting(true);
});
runOnUiThreadBlocking(
() -> {
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(
new ListItem(
CREDIT_CARD,
createCardSuggestionModel(
VISA, VISA_SUGGESTION, mItemCollectionInfo)));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
// The sheet should be expanded to full height.
pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.FULL);
// Disabling the accessibility settings.
runOnUiThreadBlocking(
() -> {
AccessibilityState.setIsTouchExplorationEnabledForTesting(false);
});
}
@Test
@MediumTest
public void testSheetStartsInHalfHeightForAccessibilityDisabled() {
runOnUiThreadBlocking(
() -> {
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(
new ListItem(
CREDIT_CARD,
createCardSuggestionModel(
VISA, VISA_SUGGESTION, mItemCollectionInfo)));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
// The sheet should be expanded to half height if possible, the half height state is
// disabled on small screens.
@BottomSheetController.SheetState
int desiredState =
mBottomSheetController.isSmallScreen()
? BottomSheetController.SheetState.FULL
: BottomSheetController.SheetState.HALF;
pollUiThread(() -> getBottomSheetState() == desiredState);
}
@Test
@MediumTest
public void testSheetScrollabilityDependsOnState() {
runOnUiThreadBlocking(
() -> {
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(
new ListItem(
CREDIT_CARD,
createCardSuggestionModel(
VISA, VISA_SUGGESTION, mItemCollectionInfo)));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
// The sheet should be expanded to the half height and scrolling suppressed, unless
// the half height state is disabled due to the device having too small a screen.
RecyclerView recyclerView = mTouchToFillPaymentMethodView.getSheetItemListView();
assertEquals(!mBottomSheetController.isSmallScreen(), recyclerView.isLayoutSuppressed());
// Expand the sheet to the full height and scrolling .
runOnUiThreadBlocking(
() ->
mSheetTestSupport.setSheetState(
BottomSheetController.SheetState.FULL, false));
BottomSheetTestSupport.waitForState(
mBottomSheetController, BottomSheetController.SheetState.FULL);
assertFalse(recyclerView.isLayoutSuppressed());
}
@Test
@MediumTest
@DisableFeatures({ChromeFeatureList.AUTOFILL_ENABLE_SECURITY_TOUCH_EVENT_FILTERING_ANDROID})
public void testCreditCardSuggestionViewProcessesClicksThroughObscuredSurfaces() {
Runnable actionCallback = mock(Runnable.class);
runOnUiThreadBlocking(
() -> {
PropertyModel cardModel =
createCardSuggestionModel(
NICKNAMED_VISA,
NICKNAMED_VISA_SUGGESTION,
mItemCollectionInfo,
actionCallback);
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(new ListItem(CREDIT_CARD, cardModel));
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(new ListItem(FILL_BUTTON, cardModel));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
onView(withText(NICKNAMED_VISA_SUGGESTION.getLabel()))
.perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
waitForEvent(actionCallback).run();
}
@Test
@MediumTest
@DisableFeatures({ChromeFeatureList.AUTOFILL_ENABLE_SECURITY_TOUCH_EVENT_FILTERING_ANDROID})
public void testAcceptButtonProcessesClicksThroughObscuredSurfaces() {
Runnable actionCallback = mock(Runnable.class);
runOnUiThreadBlocking(
() -> {
PropertyModel cardModel =
createCardSuggestionModel(
NICKNAMED_VISA,
NICKNAMED_VISA_SUGGESTION,
mItemCollectionInfo,
actionCallback);
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(new ListItem(CREDIT_CARD, cardModel));
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(new ListItem(FILL_BUTTON, cardModel));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
onView(
withText(
mActivityTestRule
.getActivity()
.getString(
R.string.autofill_payment_method_continue_button)))
.perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
waitForEvent(actionCallback).run();
}
@Test
@MediumTest
@EnableFeatures({ChromeFeatureList.AUTOFILL_ENABLE_SECURITY_TOUCH_EVENT_FILTERING_ANDROID})
public void testCreditCardSuggestionViewFiltersClicks() {
runOnUiThreadBlocking(
() -> {
PropertyModel cardModel =
createCardSuggestionModel(
NICKNAMED_VISA,
NICKNAMED_VISA_SUGGESTION,
mItemCollectionInfo,
/* actionCallback= */ () -> fail());
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(new ListItem(CREDIT_CARD, cardModel));
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(new ListItem(FILL_BUTTON, cardModel));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
// Make sure touch events are ignored if something is drawn on top the the bottom sheet.
onView(withText(NICKNAMED_VISA_SUGGESTION.getLabel()))
.perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
onView(withText(NICKNAMED_VISA_SUGGESTION.getLabel()))
.perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED));
onView(
withText(
mActivityTestRule
.getActivity()
.getString(
R.string.autofill_payment_method_continue_button)))
.perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
onView(
withText(
mActivityTestRule
.getActivity()
.getString(
R.string.autofill_payment_method_continue_button)))
.perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED));
}
@Test
@MediumTest
public void testMainTextShowsNetworkForNicknamedCard() {
runOnUiThreadBlocking(
() -> {
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(
new ListItem(
CREDIT_CARD,
createCardSuggestionModel(
NICKNAMED_VISA,
NICKNAMED_VISA_SUGGESTION,
mItemCollectionInfo)));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
TextView mainText =
mTouchToFillPaymentMethodView.getContentView().findViewById(R.id.main_text);
assertTrue(mainText.getContentDescription().toString().equals("Best Card visa"));
}
@Test
@MediumTest
public void testMainTextDescriptionIsNotSetForNonNicknamedCard() {
runOnUiThreadBlocking(
() -> {
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(
new ListItem(
CREDIT_CARD,
createCardSuggestionModel(
VISA, VISA_SUGGESTION, mItemCollectionInfo)));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
TextView mainText =
mTouchToFillPaymentMethodView.getContentView().findViewById(R.id.main_text);
assertEquals(mainText.getContentDescription(), null);
}
@Test
@MediumTest
public void testDescriptionLineContentDescriptionOfCreditCardSuggestion() {
runOnUiThreadBlocking(
() -> {
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(
new ListItem(
CREDIT_CARD,
createCardSuggestionModel(
VISA,
VISA_SUGGESTION,
new FillableItemCollectionInfo(1, 1))));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
TextView descriptionLine =
mTouchToFillPaymentMethodView
.getContentView()
.findViewById(R.id.description_line_2);
AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
descriptionLine.onInitializeAccessibilityNodeInfo(info);
assertEquals(
descriptionLine
.getContext()
.getString(
R.string.autofill_payment_method_a11y_item_collection_info,
descriptionLine.getText(),
1,
1),
info.getContentDescription());
}
@Test
@MediumTest
public void testDescriptionLineContentDescriptionOfVirtualCardSuggestion() {
runOnUiThreadBlocking(
() -> {
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(
new ListItem(
CREDIT_CARD,
createCardSuggestionModel(
VIRTUAL_CARD,
VIRTUAL_CARD_SUGGESTION,
new FillableItemCollectionInfo(1, 1))));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
TextView descriptionLine =
mTouchToFillPaymentMethodView
.getContentView()
.findViewById(R.id.description_line_2);
AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
descriptionLine.onInitializeAccessibilityNodeInfo(info);
assertEquals(
descriptionLine
.getContext()
.getString(
R.string.autofill_payment_method_a11y_item_collection_info,
descriptionLine.getText(),
1,
1),
info.getContentDescription());
}
@Test
@MediumTest
public void testNonAcceptableVirtualCardSuggestion() {
runOnUiThreadBlocking(
() -> {
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(
new ListItem(
CREDIT_CARD,
createCardSuggestionModel(
VIRTUAL_CARD,
NON_ACCEPTABLE_VIRTUAL_CARD_SUGGESTION,
new FillableItemCollectionInfo(1, 1),
() -> {})));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
ImageView icon = mTouchToFillPaymentMethodView.getContentView().findViewById(R.id.favicon);
assertThat(icon.getAlpha(), is(0.38f));
assertThat(getCreditCardSuggestions().getChildAt(0).isEnabled(), is(false));
assertThat(
getSuggestionMainTextAt(0).getText(),
is(NON_ACCEPTABLE_VIRTUAL_CARD_SUGGESTION.getLabel()));
assertThat(
getSuggestionMinorTextAt(0).getText(),
is(NON_ACCEPTABLE_VIRTUAL_CARD_SUGGESTION.getSecondaryLabel()));
assertThat(
getSuggestionLabelAt(0).getText(),
is(NON_ACCEPTABLE_VIRTUAL_CARD_SUGGESTION.getSublabel()));
}
@Test
@MediumTest
@DisabledTest(message = "crbug.com/333128685")
public void testMainTextTruncatesLongCardNameWithLastFourDigitsAlwaysShown() {
runOnUiThreadBlocking(
() -> {
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(
new ListItem(
CREDIT_CARD,
createCardSuggestionModel(
LONG_CARD_NAME_CARD,
LONG_CARD_NAME_CARD_SUGGESTION,
mItemCollectionInfo)));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
TextView mainText =
mTouchToFillPaymentMethodView.getContentView().findViewById(R.id.main_text);
TextView minorText =
mTouchToFillPaymentMethodView.getContentView().findViewById(R.id.minor_text);
assertTrue(
mainText.getLayout().getEllipsisCount(mainText.getLayout().getLineCount() - 1) > 0);
assertThat(
minorText.getLayout().getText().toString(),
is(LONG_CARD_NAME_CARD_SUGGESTION.getSecondaryLabel()));
}
@Test
@MediumTest
@DisableFeatures({ChromeFeatureList.AUTOFILL_ENABLE_SECURITY_TOUCH_EVENT_FILTERING_ANDROID})
public void testIbanViewProcessesTouchEvents() {
Runnable actionCallback = mock(Runnable.class);
runOnUiThreadBlocking(
() -> {
PropertyModel ibanModel = createIbanModel(LOCAL_IBAN, actionCallback);
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(new ListItem(IBAN, ibanModel));
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(new ListItem(FILL_BUTTON, ibanModel));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
onView(withText(LOCAL_IBAN.getLabel()))
.perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
waitForEvent(actionCallback).run();
}
@Test
@MediumTest
@DisableFeatures({ChromeFeatureList.AUTOFILL_ENABLE_SECURITY_TOUCH_EVENT_FILTERING_ANDROID})
public void testIbanAcceptButtonProcessesTouchEvents() {
Runnable actionCallback = mock(Runnable.class);
runOnUiThreadBlocking(
() -> {
PropertyModel ibanModel = createIbanModel(LOCAL_IBAN, actionCallback);
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(new ListItem(IBAN, ibanModel));
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(new ListItem(FILL_BUTTON, ibanModel));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
onView(
withText(
mActivityTestRule
.getActivity()
.getString(
R.string.autofill_payment_method_continue_button)))
.perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
waitForEvent(actionCallback).run();
}
@Test
@MediumTest
@EnableFeatures({ChromeFeatureList.AUTOFILL_ENABLE_SECURITY_TOUCH_EVENT_FILTERING_ANDROID})
public void testIbanViewFiltersTouchEvents() {
runOnUiThreadBlocking(
() -> {
PropertyModel ibanModel = createIbanModel(LOCAL_IBAN, () -> fail());
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(new ListItem(IBAN, ibanModel));
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(new ListItem(FILL_BUTTON, ibanModel));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
// Make sure touch events are ignored if something is drawn on top the the bottom sheet.
onView(withText(LOCAL_IBAN.getLabel()))
.perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
onView(withText(LOCAL_IBAN.getLabel()))
.perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED));
onView(
withText(
mActivityTestRule
.getActivity()
.getString(
R.string.autofill_payment_method_continue_button)))
.perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
onView(
withText(
mActivityTestRule
.getActivity()
.getString(
R.string.autofill_payment_method_continue_button)))
.perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED));
}
@Test
@MediumTest
public void testIbanTouchToFillItem_displayPrimaryAndSecondaryText() {
runOnUiThreadBlocking(
() -> {
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(new ListItem(IBAN, createIbanModel(LOCAL_IBAN)));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
TextView ibanPrimaryText =
mTouchToFillPaymentMethodView.getContentView().findViewById(R.id.iban_primary);
assertThat(ibanPrimaryText.getLayout().getText().toString(), is(LOCAL_IBAN.getNickname()));
TextView ibanSecondaryText =
mTouchToFillPaymentMethodView.getContentView().findViewById(R.id.iban_secondary);
assertThat(ibanSecondaryText.getLayout().getText().toString(), is(LOCAL_IBAN.getLabel()));
}
@Test
@MediumTest
public void testIbanTouchToFillItem_displayPrimaryTextOnly() {
runOnUiThreadBlocking(
() -> {
mTouchToFillPaymentMethodModel
.get(SHEET_ITEMS)
.add(new ListItem(IBAN, createIbanModel(LOCAL_IBAN_NO_NICKNAME)));
mTouchToFillPaymentMethodModel.set(VISIBLE, true);
});
BottomSheetTestSupport.waitForOpen(mBottomSheetController);
TextView ibanPrimaryText =
mTouchToFillPaymentMethodView.getContentView().findViewById(R.id.iban_primary);
assertThat(ibanPrimaryText.getLayout().getText().toString(), is(LOCAL_IBAN.getLabel()));
TextView ibanSecondaryText =
mTouchToFillPaymentMethodView.getContentView().findViewById(R.id.iban_secondary);
assertThat(ibanSecondaryText.getVisibility(), is(View.GONE));
}
private RecyclerView getCreditCardSuggestions() {
return mTouchToFillPaymentMethodView.getContentView().findViewById(R.id.sheet_item_list);
}
private TextView getSuggestionMainTextAt(int index) {
return getCreditCardSuggestions().getChildAt(index).findViewById(R.id.main_text);
}
private TextView getSuggestionMinorTextAt(int index) {
return getCreditCardSuggestions().getChildAt(index).findViewById(R.id.minor_text);
}
private TextView getSuggestionLabelAt(int index) {
return getCreditCardSuggestions().getChildAt(index).findViewById(R.id.description_line_2);
}
private @SheetState int getBottomSheetState() {
return mBottomSheetController.getSheetState();
}
private static PropertyModel createCardSuggestionModel(
CreditCard card,
AutofillSuggestion suggestion,
FillableItemCollectionInfo collectionInfo) {
return createCardSuggestionModel(card, suggestion, collectionInfo, () -> {});
}
private static PropertyModel createCardSuggestionModel(
CreditCard card,
AutofillSuggestion suggestion,
FillableItemCollectionInfo collectionInfo,
Runnable actionCallback) {
PropertyModel.Builder creditCardSuggestionModelBuilder =
new PropertyModel.Builder(NON_TRANSFORMING_CREDIT_CARD_SUGGESTION_KEYS)
.with(MAIN_TEXT, suggestion.getLabel())
.with(MINOR_TEXT, suggestion.getSecondaryLabel())
.with(FIRST_LINE_LABEL, suggestion.getSublabel())
.with(ITEM_COLLECTION_INFO, collectionInfo)
.with(ON_CREDIT_CARD_CLICK_ACTION, actionCallback)
.with(APPLY_DEACTIVATED_STYLE, suggestion.applyDeactivatedStyle());
if (!card.getBasicCardIssuerNetwork()
.equals(card.getCardNameForAutofillDisplay().toLowerCase())) {
creditCardSuggestionModelBuilder.with(NETWORK_NAME, card.getBasicCardIssuerNetwork());
}
return creditCardSuggestionModelBuilder.build();
}
private static PropertyModel createIbanModel(Iban iban) {
return createIbanModel(iban, () -> {});
}
private static PropertyModel createIbanModel(Iban iban, Runnable actionCallback) {
PropertyModel.Builder ibanModelBuilder =
new PropertyModel.Builder(NON_TRANSFORMING_IBAN_KEYS)
.with(IBAN_VALUE, iban.getLabel())
.with(IBAN_NICKNAME, iban.getNickname())
.with(ON_IBAN_CLICK_ACTION, actionCallback);
return ibanModelBuilder.build();
}
private static <T> T waitForEvent(T mock) {
return verify(
mock,
timeout(ScalableTimeout.scaleTimeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)));
}
}