1 // Copyright 2017 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.payments; 6 7 import android.os.Handler; 8 import android.view.KeyEvent; 9 import android.view.View; 10 import android.view.ViewGroup; 11 import android.view.inputmethod.EditorInfo; 12 import android.widget.CheckBox; 13 import android.widget.EditText; 14 import android.widget.Spinner; 15 import android.widget.TextView; 16 17 import androidx.annotation.IntDef; 18 19 import org.hamcrest.Matchers; 20 import org.junit.Assert; 21 import org.junit.runner.Description; 22 import org.junit.runners.model.Statement; 23 24 import org.chromium.base.ThreadUtils; 25 import org.chromium.base.metrics.RecordHistogram; 26 import org.chromium.base.task.PostTask; 27 import org.chromium.base.test.util.CallbackHelper; 28 import org.chromium.base.test.util.Criteria; 29 import org.chromium.base.test.util.CriteriaHelper; 30 import org.chromium.base.test.util.CriteriaNotSatisfiedException; 31 import org.chromium.base.test.util.UrlUtils; 32 import org.chromium.chrome.R; 33 import org.chromium.chrome.browser.autofill.CardUnmaskPrompt; 34 import org.chromium.chrome.browser.autofill.CardUnmaskPrompt.CardUnmaskObserverForTest; 35 import org.chromium.chrome.browser.autofill.prefeditor.EditorObserverForTest; 36 import org.chromium.chrome.browser.autofill.prefeditor.EditorTextField; 37 import org.chromium.chrome.browser.payments.ChromePaymentRequestFactory.ChromePaymentRequestDelegateImpl; 38 import org.chromium.chrome.browser.payments.ChromePaymentRequestFactory.ChromePaymentRequestDelegateImplObserverForTest; 39 import org.chromium.chrome.browser.payments.ui.PaymentRequestSection.OptionSection; 40 import org.chromium.chrome.browser.payments.ui.PaymentRequestSection.OptionSection.OptionRow; 41 import org.chromium.chrome.browser.payments.ui.PaymentRequestUI; 42 import org.chromium.chrome.browser.payments.ui.PaymentRequestUI.PaymentRequestObserverForTest; 43 import org.chromium.chrome.test.ChromeTabbedActivityTestRule; 44 import org.chromium.components.payments.AbortReason; 45 import org.chromium.components.payments.PayerData; 46 import org.chromium.components.payments.PaymentApp; 47 import org.chromium.components.payments.PaymentAppFactoryDelegate; 48 import org.chromium.components.payments.PaymentAppFactoryInterface; 49 import org.chromium.components.payments.PaymentAppService; 50 import org.chromium.components.payments.PaymentFeatureList; 51 import org.chromium.components.payments.PaymentRequestService; 52 import org.chromium.components.payments.PaymentRequestService.PaymentRequestServiceObserverForTest; 53 import org.chromium.content_public.browser.UiThreadTaskTraits; 54 import org.chromium.content_public.browser.WebContents; 55 import org.chromium.content_public.browser.test.util.DOMUtils; 56 import org.chromium.content_public.browser.test.util.JavaScriptUtils; 57 import org.chromium.payments.mojom.PaymentDetailsModifier; 58 import org.chromium.payments.mojom.PaymentItem; 59 import org.chromium.payments.mojom.PaymentMethodData; 60 import org.chromium.payments.mojom.PaymentOptions; 61 import org.chromium.payments.mojom.PaymentShippingOption; 62 import org.chromium.ui.modaldialog.ModalDialogProperties; 63 import org.chromium.ui.modelutil.PropertyModel; 64 65 import java.lang.annotation.Retention; 66 import java.lang.annotation.RetentionPolicy; 67 import java.util.HashSet; 68 import java.util.List; 69 import java.util.Locale; 70 import java.util.Map; 71 import java.util.Set; 72 import java.util.UUID; 73 import java.util.concurrent.TimeoutException; 74 import java.util.concurrent.atomic.AtomicReference; 75 76 /** 77 * Custom ActivityTestRule for integration test for payments. 78 */ 79 public class PaymentRequestTestRule extends ChromeTabbedActivityTestRule 80 implements PaymentRequestObserverForTest, PaymentRequestServiceObserverForTest, 81 ChromePaymentRequestDelegateImplObserverForTest, CardUnmaskObserverForTest, 82 EditorObserverForTest { 83 @IntDef({AppPresence.NO_APPS, AppPresence.HAVE_APPS}) 84 @Retention(RetentionPolicy.SOURCE) 85 public @interface AppPresence { 86 /** Flag for a factory without payment apps. */ 87 public static final int NO_APPS = 0; 88 89 /** Flag for a factory with payment apps. */ 90 public static final int HAVE_APPS = 1; 91 } 92 93 @IntDef({AppSpeed.FAST_APP, AppSpeed.SLOW_APP}) 94 @Retention(RetentionPolicy.SOURCE) 95 public @interface AppSpeed { 96 /** Flag for installing a payment app that responds to its invocation fast. */ 97 public static final int FAST_APP = 0; 98 99 /** Flag for installing a payment app that responds to its invocation slowly. */ 100 public static final int SLOW_APP = 1; 101 } 102 103 @IntDef({FactorySpeed.FAST_FACTORY, FactorySpeed.SLOW_FACTORY}) 104 @Retention(RetentionPolicy.SOURCE) 105 public @interface FactorySpeed { 106 /** Flag for a factory that immediately creates a payment app. */ 107 public static final int FAST_FACTORY = 0; 108 109 /** Flag for a factory that creates a payment app with a delay. */ 110 public static final int SLOW_FACTORY = 1; 111 } 112 113 /** The expiration month dropdown index for December. */ 114 public static final int DECEMBER = 11; 115 116 /** The expiration year dropdown index for the next year. */ 117 public static final int NEXT_YEAR = 1; 118 119 /** 120 * The billing address dropdown index for the first billing address. Index 0 is for the 121 * "Select" hint. 122 */ 123 public static final int FIRST_BILLING_ADDRESS = 1; 124 125 /** Command line flag to enable payment details modifiers in tests. */ 126 public static final String ENABLE_WEB_PAYMENTS_MODIFIERS = 127 "enable-features=" + PaymentFeatureList.WEB_PAYMENTS_MODIFIERS; 128 129 /** Command line flag to enable experimental web platform features in tests. */ 130 public static final String ENABLE_EXPERIMENTAL_WEB_PLATFORM_FEATURES = 131 "enable-experimental-web-platform-features"; 132 133 final PaymentsCallbackHelper<PaymentRequestUI> mReadyForInput; 134 final PaymentsCallbackHelper<PaymentRequestUI> mReadyToPay; 135 final PaymentsCallbackHelper<PaymentRequestUI> mSelectionChecked; 136 final PaymentsCallbackHelper<PaymentRequestUI> mResultReady; 137 final PaymentsCallbackHelper<CardUnmaskPrompt> mReadyForUnmaskInput; 138 final PaymentsCallbackHelper<CardUnmaskPrompt> mReadyToUnmask; 139 final PaymentsCallbackHelper<CardUnmaskPrompt> mUnmaskValidationDone; 140 final PaymentsCallbackHelper<CardUnmaskPrompt> mSubmitRejected; 141 final CallbackHelper mReadyToEdit; 142 final CallbackHelper mEditorValidationError; 143 final CallbackHelper mEditorTextUpdate; 144 final CallbackHelper mDismissed; 145 final CallbackHelper mUnableToAbort; 146 final CallbackHelper mBillingAddressChangeProcessed; 147 final CallbackHelper mShowFailed; 148 final CallbackHelper mCanMakePaymentQueryResponded; 149 final CallbackHelper mHasEnrolledInstrumentQueryResponded; 150 final CallbackHelper mExpirationMonthChange; 151 final CallbackHelper mPaymentResponseReady; 152 final CallbackHelper mCompleteReplied; 153 final CallbackHelper mRendererClosedMojoConnection; 154 private ChromePaymentRequestDelegateImpl mChromePaymentRequestDelegateImpl; 155 PaymentRequestUI mUI; 156 157 private final boolean mDelayStartActivity; 158 159 private final AtomicReference<WebContents> mWebContentsRef; 160 161 private final String mTestFilePath; 162 163 private CardUnmaskPrompt mCardUnmaskPrompt; 164 165 private final MainActivityStartCallback mCallback; 166 167 /** 168 * Creates an instance of PaymentRequestTestRule. 169 * @param testFileName The file name of an test page in //components/test/data/payments, 170 * 'about:blank', or a data url which starts with 'data:'. 171 */ PaymentRequestTestRule(String testFileName)172 public PaymentRequestTestRule(String testFileName) { 173 this(testFileName, null); 174 } 175 176 /** 177 * Creates an instance of PaymentRequestTestRule. 178 * @param testFileName The file name of an test page in //components/test/data/payments, 179 * 'about:blank', or a data url which starts with 'data:'. 180 * @param callback A callback that is invoked on the start of the main activity. 181 */ PaymentRequestTestRule(String testFileName, MainActivityStartCallback callback)182 public PaymentRequestTestRule(String testFileName, MainActivityStartCallback callback) { 183 this(testFileName, callback, false); 184 } 185 186 /** 187 * Creates an instance of PaymentRequestTestRule. 188 * @param testFileName The file name of an test page in //components/test/data/payments, 189 * 'about:blank', or a data url which starts with 'data:'. 190 * @param callback A callback that is invoked on the start of the main activity. 191 * @param delayStartActivity Whether to delay the start of the main activity. When true, {@link 192 * #startMainActivity()} needs to be called to start the main activity; otherwise, the 193 * main activity would start automatically. 194 */ PaymentRequestTestRule( String testFileName, MainActivityStartCallback callback, boolean delayStartActivity)195 public PaymentRequestTestRule( 196 String testFileName, MainActivityStartCallback callback, boolean delayStartActivity) { 197 this(testFileName, /*pathPrefix=*/"components/test/data/payments/", callback, 198 delayStartActivity); 199 } 200 201 /** 202 * Creates an instance of PaymentRequestTestRule with a test page, which is specified by 203 * pathPrefix and testFileName combined into a path relative to the repository root. For 204 * example, if testFileName is "merchant.html", pathPrefix is "components/test/data/payments/", 205 * the method would look for a test page at "components/test/data/payments/merchant.html". 206 * This method is used by the //clank tests. 207 * @param testFileName The file name of the test page. 208 * @param pathPrefix The prefix path to testFileName. 209 * @param delayStartActivity Whether to delay the start of the main activity. 210 * @return The created instance. 211 */ createWithPathPrefix( String testFileName, String pathPrefix, boolean delayStartActivity)212 public static PaymentRequestTestRule createWithPathPrefix( 213 String testFileName, String pathPrefix, boolean delayStartActivity) { 214 assert pathPrefix.endsWith("/"); 215 return new PaymentRequestTestRule(testFileName, pathPrefix, null, delayStartActivity); 216 } 217 PaymentRequestTestRule(String testFilePath, String pathPrefix, MainActivityStartCallback callback, boolean delayStartActivity)218 private PaymentRequestTestRule(String testFilePath, String pathPrefix, 219 MainActivityStartCallback callback, boolean delayStartActivity) { 220 super(); 221 mReadyForInput = new PaymentsCallbackHelper<>(); 222 mReadyToPay = new PaymentsCallbackHelper<>(); 223 mSelectionChecked = new PaymentsCallbackHelper<>(); 224 mResultReady = new PaymentsCallbackHelper<>(); 225 mReadyForUnmaskInput = new PaymentsCallbackHelper<>(); 226 mReadyToUnmask = new PaymentsCallbackHelper<>(); 227 mUnmaskValidationDone = new PaymentsCallbackHelper<>(); 228 mSubmitRejected = new PaymentsCallbackHelper<>(); 229 mReadyToEdit = new CallbackHelper(); 230 mEditorValidationError = new CallbackHelper(); 231 mEditorTextUpdate = new CallbackHelper(); 232 mDismissed = new CallbackHelper(); 233 mUnableToAbort = new CallbackHelper(); 234 mBillingAddressChangeProcessed = new CallbackHelper(); 235 mExpirationMonthChange = new CallbackHelper(); 236 mPaymentResponseReady = new CallbackHelper(); 237 mShowFailed = new CallbackHelper(); 238 mCanMakePaymentQueryResponded = new CallbackHelper(); 239 mHasEnrolledInstrumentQueryResponded = new CallbackHelper(); 240 mCompleteReplied = new CallbackHelper(); 241 mRendererClosedMojoConnection = new CallbackHelper(); 242 mWebContentsRef = new AtomicReference<>(); 243 if (testFilePath.equals("about:blank") || testFilePath.startsWith("data:")) { 244 mTestFilePath = testFilePath; 245 } else { 246 mTestFilePath = UrlUtils.getIsolatedTestFilePath(pathPrefix + testFilePath); 247 } 248 mCallback = callback; 249 mDelayStartActivity = delayStartActivity; 250 } 251 startMainActivity()252 public void startMainActivity() { 253 startMainActivityWithURL(mTestFilePath); 254 try { 255 // TODO(crbug.com/1144303): Figure out what these tests need to wait on to not be flaky 256 // instead of sleeping. 257 Thread.sleep(2000); 258 } catch (Exception ex) { 259 } 260 } 261 262 // public is used so as to be visible to the payment tests in //clank. openPage()263 public void openPage() throws TimeoutException { 264 onMainActivityStarted(); 265 ThreadUtils.runOnUiThreadBlocking(() -> { 266 mWebContentsRef.set(getActivity().getCurrentWebContents()); 267 PaymentRequestUI.setEditorObserverForTest(PaymentRequestTestRule.this); 268 PaymentRequestUI.setPaymentRequestObserverForTest(PaymentRequestTestRule.this); 269 PaymentRequestService.setObserverForTest(PaymentRequestTestRule.this); 270 ChromePaymentRequestFactory.setChromePaymentRequestDelegateImplObserverForTest( 271 PaymentRequestTestRule.this); 272 CardUnmaskPrompt.setObserverForTest(PaymentRequestTestRule.this); 273 }); 274 assertWaitForPageScaleFactorMatch(1); 275 } 276 getReadyForInput()277 public PaymentsCallbackHelper<PaymentRequestUI> getReadyForInput() { 278 return mReadyForInput; 279 } getReadyToPay()280 public PaymentsCallbackHelper<PaymentRequestUI> getReadyToPay() { 281 return mReadyToPay; 282 } getSelectionChecked()283 public PaymentsCallbackHelper<PaymentRequestUI> getSelectionChecked() { 284 return mSelectionChecked; 285 } getResultReady()286 public PaymentsCallbackHelper<PaymentRequestUI> getResultReady() { 287 return mResultReady; 288 } getReadyForUnmaskInput()289 public PaymentsCallbackHelper<CardUnmaskPrompt> getReadyForUnmaskInput() { 290 return mReadyForUnmaskInput; 291 } getReadyToUnmask()292 public PaymentsCallbackHelper<CardUnmaskPrompt> getReadyToUnmask() { 293 return mReadyToUnmask; 294 } getUnmaskValidationDone()295 public PaymentsCallbackHelper<CardUnmaskPrompt> getUnmaskValidationDone() { 296 return mUnmaskValidationDone; 297 } getSubmitRejected()298 public PaymentsCallbackHelper<CardUnmaskPrompt> getSubmitRejected() { 299 return mSubmitRejected; 300 } getReadyToEdit()301 public CallbackHelper getReadyToEdit() { 302 return mReadyToEdit; 303 } getEditorValidationError()304 public CallbackHelper getEditorValidationError() { 305 return mEditorValidationError; 306 } getEditorTextUpdate()307 public CallbackHelper getEditorTextUpdate() { 308 return mEditorTextUpdate; 309 } getDismissed()310 public CallbackHelper getDismissed() { 311 return mDismissed; 312 } getUnableToAbort()313 public CallbackHelper getUnableToAbort() { 314 return mUnableToAbort; 315 } getBillingAddressChangeProcessed()316 public CallbackHelper getBillingAddressChangeProcessed() { 317 return mBillingAddressChangeProcessed; 318 } getShowFailed()319 public CallbackHelper getShowFailed() { 320 return mShowFailed; 321 } getCanMakePaymentQueryResponded()322 public CallbackHelper getCanMakePaymentQueryResponded() { 323 return mCanMakePaymentQueryResponded; 324 } getHasEnrolledInstrumentQueryResponded()325 public CallbackHelper getHasEnrolledInstrumentQueryResponded() { 326 return mHasEnrolledInstrumentQueryResponded; 327 } getExpirationMonthChange()328 public CallbackHelper getExpirationMonthChange() { 329 return mExpirationMonthChange; 330 } getPaymentResponseReady()331 public CallbackHelper getPaymentResponseReady() { 332 return mPaymentResponseReady; 333 } getCompleteReplied()334 public CallbackHelper getCompleteReplied() { 335 return mCompleteReplied; 336 } getRendererClosedMojoConnection()337 public CallbackHelper getRendererClosedMojoConnection() { 338 return mRendererClosedMojoConnection; 339 } getPaymentRequestUI()340 public PaymentRequestUI getPaymentRequestUI() { 341 return mUI; 342 } 343 triggerUIAndWait(PaymentsCallbackHelper<PaymentRequestUI> helper)344 protected void triggerUIAndWait(PaymentsCallbackHelper<PaymentRequestUI> helper) 345 throws TimeoutException { 346 openPageAndClickNodeAndWait("buy", helper); 347 mUI = helper.getTarget(); 348 } 349 openPageAndClickNodeAndWait(String nodeId, CallbackHelper helper)350 protected void openPageAndClickNodeAndWait(String nodeId, CallbackHelper helper) 351 throws TimeoutException { 352 openPage(); 353 clickNodeAndWait(nodeId, helper); 354 } 355 openPageAndClickBuyAndWait(CallbackHelper helper)356 protected void openPageAndClickBuyAndWait(CallbackHelper helper) throws TimeoutException { 357 openPageAndClickNodeAndWait("buy", helper); 358 } 359 openPageAndClickNode(String nodeId)360 protected void openPageAndClickNode(String nodeId) throws TimeoutException { 361 openPage(); 362 clickNode(nodeId); 363 } 364 triggerUIAndWait(String nodeId, PaymentsCallbackHelper<PaymentRequestUI> helper)365 protected void triggerUIAndWait(String nodeId, PaymentsCallbackHelper<PaymentRequestUI> helper) 366 throws TimeoutException { 367 openPageAndClickNodeAndWait(nodeId, helper); 368 mUI = helper.getTarget(); 369 } 370 reTriggerUIAndWait(String nodeId, PaymentsCallbackHelper<PaymentRequestUI> helper)371 protected void reTriggerUIAndWait(String nodeId, 372 PaymentsCallbackHelper<PaymentRequestUI> helper) throws TimeoutException { 373 clickNodeAndWait(nodeId, helper); 374 mUI = helper.getTarget(); 375 } 376 retryPaymentRequest(String validationErrors, CallbackHelper helper)377 protected void retryPaymentRequest(String validationErrors, CallbackHelper helper) 378 throws TimeoutException { 379 int callCount = helper.getCallCount(); 380 JavaScriptUtils.executeJavaScriptAndWaitForResult( 381 mWebContentsRef.get(), "retry(" + validationErrors + ");"); 382 helper.waitForCallback(callCount); 383 } 384 executeJavaScriptAndWaitForResult(String script)385 protected String executeJavaScriptAndWaitForResult(String script) throws TimeoutException { 386 return JavaScriptUtils.executeJavaScriptAndWaitForResult(mWebContentsRef.get(), script); 387 } 388 389 // public is used so as to be visible to the payment tests in //clank. runJavascriptWithAsyncResult(String script)390 public String runJavascriptWithAsyncResult(String script) throws TimeoutException { 391 return JavaScriptUtils.runJavascriptWithAsyncResult(mWebContentsRef.get(), script); 392 } 393 394 /** Clicks on an HTML node. */ clickNodeAndWait(String nodeId, CallbackHelper helper)395 protected void clickNodeAndWait(String nodeId, CallbackHelper helper) throws TimeoutException { 396 int callCount = helper.getCallCount(); 397 clickNode(nodeId); 398 helper.waitForCallback(callCount); 399 } 400 401 /** Clicks on an HTML node. */ clickNode(String nodeId)402 protected void clickNode(String nodeId) throws TimeoutException { 403 DOMUtils.clickNode(mWebContentsRef.get(), nodeId); 404 } 405 406 /** Clicks on an element in the payments UI. */ clickAndWait(int resourceId, CallbackHelper helper)407 protected void clickAndWait(int resourceId, CallbackHelper helper) throws TimeoutException { 408 int callCount = helper.getCallCount(); 409 CriteriaHelper.pollUiThread(() -> { 410 boolean canClick = mUI.isAcceptingUserInput(); 411 if (canClick) mUI.getDialogForTest().findViewById(resourceId).performClick(); 412 Criteria.checkThat(canClick, Matchers.is(true)); 413 }); 414 helper.waitForCallback(callCount); 415 } 416 417 /** Clicks on an element in the "Order summary" section of the payments UI. */ clickInOrderSummaryAndWait(CallbackHelper helper)418 protected void clickInOrderSummaryAndWait(CallbackHelper helper) throws TimeoutException { 419 int callCount = helper.getCallCount(); 420 ThreadUtils.runOnUiThreadBlocking(() -> { 421 mUI.getOrderSummarySectionForTest().findViewById(R.id.payments_section).performClick(); 422 }); 423 helper.waitForCallback(callCount); 424 } 425 426 /** Clicks on an element in the "Shipping address" section of the payments UI. */ clickInShippingAddressAndWait(final int resourceId, CallbackHelper helper)427 protected void clickInShippingAddressAndWait(final int resourceId, CallbackHelper helper) 428 throws TimeoutException { 429 int callCount = helper.getCallCount(); 430 ThreadUtils.runOnUiThreadBlocking(() -> { 431 mUI.getShippingAddressSectionForTest().findViewById(resourceId).performClick(); 432 }); 433 helper.waitForCallback(callCount); 434 } 435 436 /** Clicks on an element in the "Payment" section of the payments UI. */ clickInPaymentMethodAndWait(final int resourceId, CallbackHelper helper)437 protected void clickInPaymentMethodAndWait(final int resourceId, CallbackHelper helper) 438 throws TimeoutException { 439 int callCount = helper.getCallCount(); 440 ThreadUtils.runOnUiThreadBlocking(() -> { 441 mUI.getPaymentMethodSectionForTest().findViewById(resourceId).performClick(); 442 }); 443 helper.waitForCallback(callCount); 444 } 445 446 /** Clicks on an element in the "Contact Info" section of the payments UI. */ clickInContactInfoAndWait(final int resourceId, CallbackHelper helper)447 protected void clickInContactInfoAndWait(final int resourceId, CallbackHelper helper) 448 throws TimeoutException { 449 int callCount = helper.getCallCount(); 450 ThreadUtils.runOnUiThreadBlocking(() -> { 451 mUI.getContactDetailsSectionForTest().findViewById(resourceId).performClick(); 452 }); 453 helper.waitForCallback(callCount); 454 } 455 456 /** Clicks on an element in the editor UI for credit cards. */ clickInCardEditorAndWait(final int resourceId, CallbackHelper helper)457 protected void clickInCardEditorAndWait(final int resourceId, CallbackHelper helper) 458 throws TimeoutException { 459 int callCount = helper.getCallCount(); 460 ThreadUtils.runOnUiThreadBlocking( 461 () -> { mUI.getCardEditorDialog().findViewById(resourceId).performClick(); }); 462 helper.waitForCallback(callCount); 463 } 464 465 /** Clicks on an element in the editor UI. */ clickInEditorAndWait(final int resourceId, CallbackHelper helper)466 protected void clickInEditorAndWait(final int resourceId, CallbackHelper helper) 467 throws TimeoutException { 468 int callCount = helper.getCallCount(); 469 ThreadUtils.runOnUiThreadBlocking( 470 () -> { mUI.getEditorDialog().findViewById(resourceId).performClick(); }); 471 helper.waitForCallback(callCount); 472 } 473 clickAndroidBackButtonInEditorAndWait(CallbackHelper helper)474 protected void clickAndroidBackButtonInEditorAndWait(CallbackHelper helper) 475 throws TimeoutException { 476 int callCount = helper.getCallCount(); 477 PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> { 478 mUI.getEditorDialog().dispatchKeyEvent( 479 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)); 480 mUI.getEditorDialog().dispatchKeyEvent( 481 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)); 482 }); 483 helper.waitForCallback(callCount); 484 } 485 486 /** Clicks on a button in the card unmask UI. */ clickCardUnmaskButtonAndWait(final int dialogButtonId, CallbackHelper helper)487 protected void clickCardUnmaskButtonAndWait(final int dialogButtonId, CallbackHelper helper) 488 throws TimeoutException { 489 int callCount = helper.getCallCount(); 490 ThreadUtils.runOnUiThreadBlocking(() -> { 491 PropertyModel model = mCardUnmaskPrompt.getDialogForTest(); 492 model.get(ModalDialogProperties.CONTROLLER).onClick(model, dialogButtonId); 493 }); 494 helper.waitForCallback(callCount); 495 } 496 497 /** Gets the retry error message. */ getRetryErrorMessage()498 protected String getRetryErrorMessage() { 499 return ThreadUtils.runOnUiThreadBlockingNoException( 500 () 501 -> ((TextView) mUI.getDialogForTest().findViewById(R.id.retry_error)) 502 .getText() 503 .toString()); 504 } 505 506 /** Gets the button state for the shipping summary section. */ getShippingAddressSectionButtonState()507 protected int getShippingAddressSectionButtonState() { 508 return ThreadUtils.runOnUiThreadBlockingNoException( 509 () -> mUI.getShippingAddressSectionForTest().getEditButtonState()); 510 } 511 512 /** Gets the button state for the contact details section. */ getContactDetailsButtonState()513 protected int getContactDetailsButtonState() { 514 return ThreadUtils.runOnUiThreadBlockingNoException( 515 () -> mUI.getContactDetailsSectionForTest().getEditButtonState()); 516 } 517 518 /** Returns the label of the payment app at the specified |index|. */ getPaymentAppLabel(final int index)519 protected String getPaymentAppLabel(final int index) { 520 return ThreadUtils.runOnUiThreadBlockingNoException( 521 () 522 -> ((OptionSection) mUI.getPaymentMethodSectionForTest()) 523 .getOptionLabelsForTest(index) 524 .getText() 525 .toString()); 526 } 527 528 /** Returns the label of the selected payment app. */ getSelectedPaymentAppLabel()529 protected String getSelectedPaymentAppLabel() { 530 return ThreadUtils.runOnUiThreadBlockingNoException(() -> { 531 OptionSection section = ((OptionSection) mUI.getPaymentMethodSectionForTest()); 532 int size = section.getNumberOfOptionLabelsForTest(); 533 for (int i = 0; i < size; i++) { 534 if (section.getOptionRowAtIndex(i).isChecked()) { 535 return section.getOptionRowAtIndex(i).getLabelText().toString(); 536 } 537 } 538 return null; 539 }); 540 } 541 542 /** Returns the total amount in order summary section. */ getOrderSummaryTotal()543 protected String getOrderSummaryTotal() { 544 return ThreadUtils.runOnUiThreadBlockingNoException( 545 () -> mUI.getOrderSummaryTotalTextViewForTest().getText().toString()); 546 } 547 548 /** Returns the amount text corresponding to the line item at the specified |index|. */ getLineItemAmount(int index)549 protected String getLineItemAmount(int index) { 550 return ThreadUtils.runOnUiThreadBlockingNoException( 551 () 552 -> mUI.getOrderSummarySectionForTest() 553 .getLineItemAmountForTest(index) 554 .getText() 555 .toString() 556 .trim()); 557 } 558 559 /** Returns the amount text corresponding to the line item at the specified |index|. */ getNumberOfLineItems()560 protected int getNumberOfLineItems() { 561 return ThreadUtils.runOnUiThreadBlockingNoException( 562 () -> mUI.getOrderSummarySectionForTest().getNumberOfLineItemsForTest()); 563 } 564 565 /** 566 * Returns the label corresponding to the contact detail suggestion at the specified 567 * |suggestionIndex|. 568 */ getContactDetailsSuggestionLabel(final int suggestionIndex)569 protected String getContactDetailsSuggestionLabel(final int suggestionIndex) { 570 return ThreadUtils.runOnUiThreadBlockingNoException( 571 () 572 -> ((OptionSection) mUI.getContactDetailsSectionForTest()) 573 .getOptionLabelsForTest(suggestionIndex) 574 .getText() 575 .toString()); 576 } 577 578 /** Returns the number of payment apps. */ getNumberOfPaymentApps()579 protected int getNumberOfPaymentApps() { 580 return ThreadUtils.runOnUiThreadBlockingNoException( 581 () 582 -> ((OptionSection) mUI.getPaymentMethodSectionForTest()) 583 .getNumberOfOptionLabelsForTest()); 584 } 585 586 /** 587 * Returns the label corresponding to the payment method suggestion at the specified 588 * |suggestionIndex|. 589 */ getPaymentMethodSuggestionLabel(final int suggestionIndex)590 protected String getPaymentMethodSuggestionLabel(final int suggestionIndex) { 591 Assert.assertTrue(suggestionIndex < getNumberOfPaymentApps()); 592 593 return ThreadUtils.runOnUiThreadBlockingNoException( 594 () 595 -> ((OptionSection) mUI.getPaymentMethodSectionForTest()) 596 .getOptionLabelsForTest(suggestionIndex) 597 .getText() 598 .toString()); 599 } 600 601 /** Returns the number of contact detail suggestions. */ getNumberOfContactDetailSuggestions()602 protected int getNumberOfContactDetailSuggestions() { 603 return ThreadUtils.runOnUiThreadBlockingNoException( 604 () 605 -> ((OptionSection) mUI.getContactDetailsSectionForTest()) 606 .getNumberOfOptionLabelsForTest()); 607 } 608 609 /** 610 * Returns the label corresponding to the shipping address suggestion at the specified 611 * |suggestionIndex|. 612 */ getShippingAddressSuggestionLabel(final int suggestionIndex)613 protected String getShippingAddressSuggestionLabel(final int suggestionIndex) { 614 Assert.assertTrue(suggestionIndex < getNumberOfShippingAddressSuggestions()); 615 616 return ThreadUtils.runOnUiThreadBlockingNoException( 617 () 618 -> mUI.getShippingAddressSectionForTest() 619 .getOptionLabelsForTest(suggestionIndex) 620 .getText() 621 .toString()); 622 } 623 getShippingAddressSummary()624 protected String getShippingAddressSummary() { 625 return ThreadUtils.runOnUiThreadBlockingNoException( 626 () 627 -> mUI.getShippingAddressSectionForTest() 628 .getLeftSummaryLabelForTest() 629 .getText() 630 .toString()); 631 } 632 getShippingOptionSummary()633 protected String getShippingOptionSummary() { 634 return ThreadUtils.runOnUiThreadBlockingNoException( 635 () 636 -> mUI.getShippingOptionSectionForTest() 637 .getLeftSummaryLabelForTest() 638 .getText() 639 .toString()); 640 } 641 getShippingOptionCostSummaryOnBottomSheet()642 protected String getShippingOptionCostSummaryOnBottomSheet() { 643 return ThreadUtils.runOnUiThreadBlockingNoException( 644 () 645 -> mUI.getShippingOptionSectionForTest() 646 .getRightSummaryLabelForTest() 647 .getText() 648 .toString()); 649 } 650 getShippingAddressWarningLabel()651 protected String getShippingAddressWarningLabel() { 652 return ThreadUtils.runOnUiThreadBlockingNoException(() -> { 653 View view = mUI.getShippingAddressSectionForTest().findViewById( 654 R.id.payments_warning_label); 655 return view != null && view instanceof TextView ? ((TextView) view).getText().toString() 656 : null; 657 }); 658 } 659 660 protected String getShippingAddressDescriptionLabel() { 661 return ThreadUtils.runOnUiThreadBlockingNoException(() -> { 662 View view = mUI.getShippingAddressSectionForTest().findViewById( 663 R.id.payments_description_label); 664 return view != null && view instanceof TextView ? ((TextView) view).getText().toString() 665 : null; 666 }); 667 } 668 669 /** Returns the focused view in the card editor view. */ 670 protected View getCardEditorFocusedView() { 671 return mUI.getCardEditorDialog().getCurrentFocus(); 672 } 673 674 /** 675 * Clicks on the label corresponding to the shipping address suggestion at the specified 676 * |suggestionIndex|. 677 */ 678 protected void clickOnShippingAddressSuggestionOptionAndWait( 679 final int suggestionIndex, CallbackHelper helper) throws TimeoutException { 680 Assert.assertTrue(suggestionIndex < getNumberOfShippingAddressSuggestions()); 681 682 int callCount = helper.getCallCount(); 683 ThreadUtils.runOnUiThreadBlocking(() -> { 684 ((OptionSection) mUI.getShippingAddressSectionForTest()) 685 .getOptionLabelsForTest(suggestionIndex) 686 .performClick(); 687 }); 688 helper.waitForCallback(callCount); 689 } 690 691 /** 692 * Clicks on the label corresponding to the payment method suggestion at the specified 693 * |suggestionIndex|. 694 */ 695 protected void clickOnPaymentMethodSuggestionOptionAndWait( 696 final int suggestionIndex, CallbackHelper helper) throws TimeoutException { 697 Assert.assertTrue(suggestionIndex < getNumberOfPaymentApps()); 698 699 int callCount = helper.getCallCount(); 700 ThreadUtils.runOnUiThreadBlocking(() -> { 701 ((OptionSection) mUI.getPaymentMethodSectionForTest()) 702 .getOptionLabelsForTest(suggestionIndex) 703 .performClick(); 704 }); 705 helper.waitForCallback(callCount); 706 } 707 708 /** 709 * Clicks on the label corresponding to the contact info suggestion at the specified 710 * |suggestionIndex|. 711 */ 712 protected void clickOnContactInfoSuggestionOptionAndWait( 713 final int suggestionIndex, CallbackHelper helper) throws TimeoutException { 714 Assert.assertTrue(suggestionIndex < getNumberOfContactDetailSuggestions()); 715 716 int callCount = helper.getCallCount(); 717 ThreadUtils.runOnUiThreadBlocking(() -> { 718 ((OptionSection) mUI.getContactDetailsSectionForTest()) 719 .getOptionLabelsForTest(suggestionIndex) 720 .performClick(); 721 }); 722 helper.waitForCallback(callCount); 723 } 724 725 /** 726 * Clicks on the edit icon corresponding to the payment method suggestion at the specified 727 * |suggestionIndex|. 728 */ 729 protected void clickOnPaymentMethodSuggestionEditIconAndWait( 730 final int suggestionIndex, CallbackHelper helper) throws TimeoutException { 731 Assert.assertTrue(suggestionIndex < getNumberOfPaymentApps()); 732 733 int callCount = helper.getCallCount(); 734 ThreadUtils.runOnUiThreadBlocking(() -> { 735 ((OptionSection) mUI.getPaymentMethodSectionForTest()) 736 .getOptionRowAtIndex(suggestionIndex) 737 .getEditIconForTest() 738 .performClick(); 739 }); 740 helper.waitForCallback(callCount); 741 } 742 743 /** 744 * Returns the summary text of the shipping address section. 745 */ 746 protected String getShippingAddressSummaryLabel() { 747 return getShippingAddressSummary(); 748 } 749 750 /** 751 * Returns the summary text of the shipping option section. 752 */ 753 protected String getShippingOptionSummaryLabel() { 754 return getShippingOptionSummary(); 755 } 756 757 /** 758 * Returns the cost text of the shipping option section on the bottom sheet. 759 */ 760 protected String getShippingOptionCostSummaryLabelOnBottomSheet() { 761 return getShippingOptionCostSummaryOnBottomSheet(); 762 } 763 764 /** 765 * Returns the number of shipping address suggestions. 766 */ 767 protected int getNumberOfShippingAddressSuggestions() { 768 return ThreadUtils.runOnUiThreadBlockingNoException( 769 () 770 -> ((OptionSection) mUI.getShippingAddressSectionForTest()) 771 .getNumberOfOptionLabelsForTest()); 772 } 773 774 /** Returns the {@link OptionRow} at the given index for the shipping address section. */ 775 protected OptionRow getShippingAddressOptionRowAtIndex(final int index) { 776 return ThreadUtils.runOnUiThreadBlockingNoException( 777 () 778 -> ((OptionSection) mUI.getShippingAddressSectionForTest()) 779 .getOptionRowAtIndex(index)); 780 } 781 782 /** Returns the selected spinner value in the editor UI for credit cards. */ 783 protected String getSpinnerSelectionTextInCardEditor(final int dropdownIndex) { 784 return ThreadUtils.runOnUiThreadBlockingNoException( 785 () 786 -> mUI.getCardEditorDialog() 787 .getDropdownFieldsForTest() 788 .get(dropdownIndex) 789 .getSelectedItem() 790 .toString()); 791 } 792 793 /** Returns the spinner value at the specified position in the editor UI for credit cards. */ 794 protected String getSpinnerTextAtPositionInCardEditor( 795 final int dropdownIndex, final int itemPosition) { 796 return ThreadUtils.runOnUiThreadBlockingNoException( 797 () 798 -> mUI.getCardEditorDialog() 799 .getDropdownFieldsForTest() 800 .get(dropdownIndex) 801 .getItemAtPosition(itemPosition) 802 .toString()); 803 } 804 805 /** Returns the number of items offered by the spinner in the editor UI for credit cards. */ 806 protected int getSpinnerItemCountInCardEditor(final int dropdownIndex) { 807 return ThreadUtils.runOnUiThreadBlockingNoException( 808 () 809 -> mUI.getCardEditorDialog() 810 .getDropdownFieldsForTest() 811 .get(dropdownIndex) 812 .getCount()); 813 } 814 815 /** Returns the error message visible to the user in the credit card unmask prompt. */ 816 protected String getUnmaskPromptErrorMessage() { 817 return mCardUnmaskPrompt.getErrorMessage(); 818 } 819 820 /** Selects the spinner value in the editor UI for credit cards. */ 821 protected void setSpinnerSelectionsInCardEditorAndWait( 822 final int[] selections, CallbackHelper helper) throws TimeoutException { 823 int callCount = helper.getCallCount(); 824 ThreadUtils.runOnUiThreadBlocking(() -> { 825 List<Spinner> fields = mUI.getCardEditorDialog().getDropdownFieldsForTest(); 826 for (int i = 0; i < selections.length && i < fields.size(); i++) { 827 fields.get(i).setSelection(selections[i]); 828 } 829 }); 830 helper.waitForCallback(callCount); 831 } 832 833 /** Selects the spinner value in the editor UI. */ 834 protected void setSpinnerSelectionInEditorAndWait(final int selection, CallbackHelper helper) 835 throws TimeoutException { 836 int callCount = helper.getCallCount(); 837 ThreadUtils.runOnUiThreadBlocking( 838 () 839 -> ((Spinner) mUI.getEditorDialog().findViewById(R.id.spinner)) 840 .setSelection(selection)); 841 helper.waitForCallback(callCount); 842 } 843 844 /** Directly sets the text in the editor UI for credit cards. */ 845 protected void setTextInCardEditorAndWait(final String[] values, CallbackHelper helper) 846 throws TimeoutException { 847 int callCount = helper.getCallCount(); 848 ThreadUtils.runOnUiThreadBlocking(() -> { 849 ViewGroup contents = (ViewGroup) mUI.getCardEditorDialog().findViewById(R.id.contents); 850 Assert.assertNotNull(contents); 851 for (int i = 0, j = 0; i < contents.getChildCount() && j < values.length; i++) { 852 View view = contents.getChildAt(i); 853 if (view instanceof EditorTextField) { 854 ((EditorTextField) view).getEditText().setText(values[j++]); 855 } 856 } 857 }); 858 helper.waitForCallback(callCount); 859 } 860 861 /** Directly sets the text in the editor UI. */ 862 protected void setTextInEditorAndWait(final String[] values, CallbackHelper helper) 863 throws TimeoutException { 864 int callCount = helper.getCallCount(); 865 ThreadUtils.runOnUiThreadBlocking(() -> { 866 List<EditText> fields = mUI.getEditorDialog().getEditableTextFieldsForTest(); 867 for (int i = 0; i < values.length; i++) { 868 fields.get(i).requestFocus(); 869 fields.get(i).setText(values[i]); 870 } 871 }); 872 helper.waitForCallback(callCount); 873 } 874 875 /** Directly sets the checkbox selection in the editor UI for credit cards. */ 876 protected void selectCheckboxAndWait(final int resourceId, final boolean isChecked, 877 CallbackHelper helper) throws TimeoutException { 878 int callCount = helper.getCallCount(); 879 ThreadUtils.runOnUiThreadBlocking( 880 () 881 -> ((CheckBox) mUI.getCardEditorDialog().findViewById(resourceId)) 882 .setChecked(isChecked)); 883 helper.waitForCallback(callCount); 884 } 885 886 /** Directly sets the text in the card unmask UI. */ 887 protected void setTextInCardUnmaskDialogAndWait(final int resourceId, final String input, 888 CallbackHelper helper) throws TimeoutException { 889 int callCount = helper.getCallCount(); 890 ThreadUtils.runOnUiThreadBlocking(() -> { 891 EditText editText = mCardUnmaskPrompt.getDialogForTest() 892 .get(ModalDialogProperties.CUSTOM_VIEW) 893 .findViewById(resourceId); 894 editText.setText(input); 895 editText.getOnFocusChangeListener().onFocusChange(null, false); 896 }); 897 helper.waitForCallback(callCount); 898 } 899 900 /** Directly sets the text in the expired card unmask UI. */ 901 protected void setTextInExpiredCardUnmaskDialogAndWait(final int[] resourceIds, 902 final String[] values, CallbackHelper helper) throws TimeoutException { 903 assert resourceIds.length == values.length; 904 int callCount = helper.getCallCount(); 905 ThreadUtils.runOnUiThreadBlocking(() -> { 906 for (int i = 0; i < resourceIds.length; ++i) { 907 EditText editText = mCardUnmaskPrompt.getDialogForTest() 908 .get(ModalDialogProperties.CUSTOM_VIEW) 909 .findViewById(resourceIds[i]); 910 editText.setText(values[i]); 911 editText.getOnFocusChangeListener().onFocusChange(null, false); 912 } 913 }); 914 helper.waitForCallback(callCount); 915 } 916 917 /** Focues a view and hits the "submit" button on the software keyboard. */ 918 /* package */ void hitSoftwareKeyboardSubmitButtonAndWait( 919 final int resourceId, CallbackHelper helper) throws TimeoutException { 920 int callCount = helper.getCallCount(); 921 ThreadUtils.runOnUiThreadBlocking(() -> { 922 EditText editText = mCardUnmaskPrompt.getDialogForTest() 923 .get(ModalDialogProperties.CUSTOM_VIEW) 924 .findViewById(resourceId); 925 editText.requestFocus(); 926 editText.onEditorAction(EditorInfo.IME_ACTION_DONE); 927 }); 928 helper.waitForCallback(callCount); 929 } 930 931 /** Verifies the contents of the test webpage. */ 932 protected void expectResultContains(final String[] contents) { 933 CriteriaHelper.pollInstrumentationThread(() -> { 934 try { 935 String result = DOMUtils.getNodeContents(mWebContentsRef.get(), "result"); 936 Criteria.checkThat( 937 "Cannot find 'result' node on test page", result, Matchers.notNullValue()); 938 for (int i = 0; i < contents.length; i++) { 939 Criteria.checkThat( 940 "Result '" + result + "' should contain '" + contents[i] + "'", result, 941 Matchers.containsString(contents[i])); 942 } 943 } catch (TimeoutException e2) { 944 throw new CriteriaNotSatisfiedException(e2); 945 } 946 }); 947 } 948 949 /** Will fail if the OptionRow at |index| is not selected in Contact Details.*/ 950 protected void expectContactDetailsRowIsSelected(final int index) { 951 CriteriaHelper.pollInstrumentationThread(() -> { 952 boolean isSelected = ((OptionSection) mUI.getContactDetailsSectionForTest()) 953 .getOptionRowAtIndex(index) 954 .isChecked(); 955 Criteria.checkThat("Contact Details row at " + index + " was not selected.", isSelected, 956 Matchers.is(true)); 957 }); 958 } 959 960 /** Will fail if the OptionRow at |index| is not selected in Shipping Address section.*/ 961 protected void expectShippingAddressRowIsSelected(final int index) { 962 CriteriaHelper.pollInstrumentationThread(() -> { 963 boolean isSelected = ((OptionSection) mUI.getShippingAddressSectionForTest()) 964 .getOptionRowAtIndex(index) 965 .isChecked(); 966 Criteria.checkThat("Shipping Address row at " + index + " was not selected.", 967 isSelected, Matchers.is(true)); 968 }); 969 } 970 971 /** Will fail if the OptionRow at |index| is not selected in PaymentMethod section.*/ 972 protected void expectPaymentMethodRowIsSelected(final int index) { 973 CriteriaHelper.pollInstrumentationThread(() -> { 974 boolean isSelected = ((OptionSection) mUI.getPaymentMethodSectionForTest()) 975 .getOptionRowAtIndex(index) 976 .isChecked(); 977 Criteria.checkThat("Payment Method row at " + index + " was not selected.", isSelected, 978 Matchers.is(true)); 979 }); 980 } 981 982 /** 983 * Asserts that only the specified reason for abort is logged. 984 * 985 * @param abortReason The only bucket in the abort histogram that should have a record. 986 */ 987 protected void assertOnlySpecificAbortMetricLogged(int abortReason) { 988 for (int i = 0; i < AbortReason.MAX; ++i) { 989 Assert.assertEquals( 990 String.format(Locale.getDefault(), "Found %d instead of %d", i, abortReason), 991 (i == abortReason ? 1 : 0), 992 RecordHistogram.getHistogramValueCountForTesting( 993 "PaymentRequest.CheckoutFunnel.Aborted", i)); 994 } 995 } 996 997 /* package */ View getPaymentRequestView() { 998 return ThreadUtils.runOnUiThreadBlockingNoException( 999 () -> mUI.getDialogForTest().findViewById(R.id.payment_request)); 1000 } 1001 1002 /* package */ View getCardUnmaskView() throws Throwable { 1003 return ThreadUtils.runOnUiThreadBlocking( 1004 () 1005 -> mCardUnmaskPrompt.getDialogForTest() 1006 .get(ModalDialogProperties.CUSTOM_VIEW) 1007 .findViewById(R.id.autofill_card_unmask_prompt)); 1008 } 1009 1010 /* package */ View getEditorDialogView() throws Throwable { 1011 return ThreadUtils.runOnUiThreadBlocking( 1012 () -> mUI.getEditorDialog().findViewById(R.id.editor_container)); 1013 } 1014 1015 /** Allows to skip UI into paymenthandler for"basic-card". */ 1016 protected void enableSkipUIForBasicCard() { 1017 ThreadUtils.runOnUiThreadBlocking( 1018 () -> mChromePaymentRequestDelegateImpl.setSkipUiForBasicCard()); 1019 } 1020 1021 @Override 1022 public void onPaymentRequestReadyForInput(PaymentRequestUI ui) { 1023 ThreadUtils.assertOnUiThread(); 1024 // This happens when the payment request is created by a direct js function call rather than 1025 // calling the js function via triggerUIAndWait() which sets the mUI. 1026 if (mUI == null) mUI = ui; 1027 mReadyForInput.notifyCalled(ui); 1028 } 1029 1030 @Override 1031 public void onEditorReadyToEdit() { 1032 ThreadUtils.assertOnUiThread(); 1033 mReadyToEdit.notifyCalled(); 1034 } 1035 1036 @Override 1037 public void onEditorValidationError() { 1038 ThreadUtils.assertOnUiThread(); 1039 mEditorValidationError.notifyCalled(); 1040 } 1041 1042 @Override 1043 public void onEditorTextUpdate() { 1044 ThreadUtils.assertOnUiThread(); 1045 mEditorTextUpdate.notifyCalled(); 1046 } 1047 1048 @Override 1049 public void onPaymentRequestReadyToPay(PaymentRequestUI ui) { 1050 ThreadUtils.assertOnUiThread(); 1051 // This happens when the payment request is created by a direct js function call rather than 1052 // calling the js function via triggerUIAndWait() which sets the mUI. 1053 if (mUI == null) mUI = ui; 1054 mReadyToPay.notifyCalled(ui); 1055 } 1056 1057 @Override 1058 public void onPaymentRequestSelectionChecked(PaymentRequestUI ui) { 1059 ThreadUtils.assertOnUiThread(); 1060 mSelectionChecked.notifyCalled(ui); 1061 } 1062 1063 @Override 1064 public void onPaymentRequestResultReady(PaymentRequestUI ui) { 1065 ThreadUtils.assertOnUiThread(); 1066 mResultReady.notifyCalled(ui); 1067 } 1068 1069 @Override 1070 public void onEditorDismiss() { 1071 ThreadUtils.assertOnUiThread(); 1072 mDismissed.notifyCalled(); 1073 } 1074 1075 @Override 1076 public void onCreatedChromePaymentRequestDelegateImpl( 1077 ChromePaymentRequestDelegateImpl delegateImpl) { 1078 ThreadUtils.assertOnUiThread(); 1079 mChromePaymentRequestDelegateImpl = delegateImpl; 1080 } 1081 1082 @Override 1083 public void onPaymentRequestServiceUnableToAbort() { 1084 ThreadUtils.assertOnUiThread(); 1085 mUnableToAbort.notifyCalled(); 1086 } 1087 1088 @Override 1089 public void onPaymentRequestServiceBillingAddressChangeProcessed() { 1090 ThreadUtils.assertOnUiThread(); 1091 mBillingAddressChangeProcessed.notifyCalled(); 1092 } 1093 1094 @Override 1095 public void onPaymentRequestServiceExpirationMonthChange() { 1096 ThreadUtils.assertOnUiThread(); 1097 mExpirationMonthChange.notifyCalled(); 1098 } 1099 1100 @Override 1101 public void onPaymentRequestServiceShowFailed() { 1102 ThreadUtils.assertOnUiThread(); 1103 mShowFailed.notifyCalled(); 1104 } 1105 1106 @Override 1107 public void onPaymentRequestServiceCanMakePaymentQueryResponded() { 1108 ThreadUtils.assertOnUiThread(); 1109 mCanMakePaymentQueryResponded.notifyCalled(); 1110 } 1111 1112 @Override 1113 public void onPaymentRequestServiceHasEnrolledInstrumentQueryResponded() { 1114 ThreadUtils.assertOnUiThread(); 1115 mHasEnrolledInstrumentQueryResponded.notifyCalled(); 1116 } 1117 1118 @Override 1119 public void onCardUnmaskPromptReadyForInput(CardUnmaskPrompt prompt) { 1120 ThreadUtils.assertOnUiThread(); 1121 mReadyForUnmaskInput.notifyCalled(prompt); 1122 mCardUnmaskPrompt = prompt; 1123 } 1124 1125 @Override 1126 public void onCardUnmaskPromptReadyToUnmask(CardUnmaskPrompt prompt) { 1127 ThreadUtils.assertOnUiThread(); 1128 mReadyToUnmask.notifyCalled(prompt); 1129 } 1130 1131 @Override 1132 public void onCardUnmaskPromptValidationDone(CardUnmaskPrompt prompt) { 1133 ThreadUtils.assertOnUiThread(); 1134 mUnmaskValidationDone.notifyCalled(prompt); 1135 } 1136 1137 @Override 1138 public void onCardUnmaskPromptSubmitRejected(CardUnmaskPrompt prompt) { 1139 ThreadUtils.assertOnUiThread(); 1140 mSubmitRejected.notifyCalled(prompt); 1141 } 1142 1143 @Override 1144 public void onPaymentResponseReady() { 1145 ThreadUtils.assertOnUiThread(); 1146 mPaymentResponseReady.notifyCalled(); 1147 } 1148 1149 @Override 1150 public void onCompleteReplied() { 1151 ThreadUtils.assertOnUiThread(); 1152 mCompleteReplied.notifyCalled(); 1153 } 1154 1155 @Override 1156 public void onRendererClosedMojoConnection() { 1157 ThreadUtils.assertOnUiThread(); 1158 mRendererClosedMojoConnection.notifyCalled(); 1159 } 1160 1161 /** 1162 * Listens for UI notifications. 1163 */ 1164 static class PaymentsCallbackHelper<T> extends CallbackHelper { 1165 private T mTarget; 1166 1167 /** 1168 * Returns the UI that is ready for input. 1169 * 1170 * @return The UI that is ready for input. 1171 */ 1172 public T getTarget() { 1173 return mTarget; 1174 } 1175 1176 /** 1177 * Called when the UI is ready for input. 1178 * 1179 * @param target The UI that is ready for input. 1180 */ 1181 public void notifyCalled(T target) { 1182 ThreadUtils.assertOnUiThread(); 1183 mTarget = target; 1184 notifyCalled(); 1185 } 1186 } 1187 1188 /** 1189 * Adds a payment app factory for testing. 1190 * 1191 * @param appPresence Whether the factory has apps. 1192 * @param factorySpeed How quick the factory creates apps. 1193 * @return The test factory. Can be ignored. 1194 */ 1195 /* package */ TestFactory addPaymentAppFactory( 1196 @AppPresence int appPresence, @FactorySpeed int factorySpeed) { 1197 return addPaymentAppFactory("https://bobpay.com", appPresence, factorySpeed); 1198 } 1199 1200 /** 1201 * Adds a payment app factory for testing. 1202 * 1203 * @param methodName The name of the payment method used in the payment app. 1204 * @param appPresence Whether the factory has apps. 1205 * @param factorySpeed How quick the factory creates apps. 1206 * @return The test factory. Can be ignored. 1207 */ 1208 /* package */ TestFactory addPaymentAppFactory( 1209 String methodName, @AppPresence int appPresence, @FactorySpeed int factorySpeed) { 1210 return addPaymentAppFactory(methodName, appPresence, factorySpeed, AppSpeed.FAST_APP); 1211 } 1212 1213 /** 1214 * Adds a payment app factory for testing. 1215 * 1216 * @param methodName The name of the payment method used in the payment app. 1217 * @param appPresence Whether the factory has apps. 1218 * @param factorySpeed How quick the factory creates apps. 1219 * @param appSpeed How quick the app responds to "invoke". 1220 * @return The test factory. Can be ignored. 1221 */ 1222 /* package */ TestFactory addPaymentAppFactory(String appMethodName, int appPresence, 1223 @FactorySpeed int factorySpeed, @AppSpeed int appSpeed) { 1224 TestFactory factory = new TestFactory(appMethodName, appPresence, factorySpeed, appSpeed); 1225 PaymentAppService.getInstance().addFactory(factory); 1226 return factory; 1227 } 1228 1229 /** A payment app factory implementation for test. */ 1230 /* package */ static final class TestFactory implements PaymentAppFactoryInterface { 1231 private final String mAppMethodName; 1232 private final @AppPresence int mAppPresence; 1233 private final @FactorySpeed int mFactorySpeed; 1234 private final @AppSpeed int mAppSpeed; 1235 private PaymentAppFactoryDelegate mDelegate; 1236 1237 private TestFactory(String appMethodName, @AppPresence int appPresence, 1238 @FactorySpeed int factorySpeed, @AppSpeed int appSpeed) { 1239 mAppMethodName = appMethodName; 1240 mAppPresence = appPresence; 1241 mFactorySpeed = factorySpeed; 1242 mAppSpeed = appSpeed; 1243 } 1244 1245 @Override 1246 public void create(PaymentAppFactoryDelegate delegate) { 1247 Runnable createApp = () -> { 1248 boolean canMakePayment = 1249 delegate.getParams().getMethodData().containsKey(mAppMethodName); 1250 delegate.onCanMakePaymentCalculated(canMakePayment); 1251 if (canMakePayment && mAppPresence == AppPresence.HAVE_APPS) { 1252 delegate.onPaymentAppCreated(new TestPay(mAppMethodName, mAppSpeed)); 1253 } 1254 delegate.onDoneCreatingPaymentApps(this); 1255 }; 1256 if (mFactorySpeed == FactorySpeed.FAST_FACTORY) { 1257 createApp.run(); 1258 } else { 1259 new Handler().postDelayed(createApp, 100); 1260 } 1261 mDelegate = delegate; 1262 } 1263 1264 /* package */ PaymentAppFactoryDelegate getDelegateForTest() { 1265 return mDelegate; 1266 } 1267 } 1268 1269 /** A payment app implementation for test. */ 1270 /* package */ static final class TestPay extends PaymentApp { 1271 private final String mDefaultMethodName; 1272 private final @AppSpeed int mAppSpeed; 1273 1274 TestPay(String defaultMethodName, @AppSpeed int appSpeed) { 1275 super(/*id=*/UUID.randomUUID().toString(), /*label=*/defaultMethodName, 1276 /*sublabel=*/null, /*icon=*/null); 1277 mDefaultMethodName = defaultMethodName; 1278 mAppSpeed = appSpeed; 1279 } 1280 1281 @Override 1282 public Set<String> getInstrumentMethodNames() { 1283 Set<String> result = new HashSet<>(); 1284 result.add(mDefaultMethodName); 1285 return result; 1286 } 1287 1288 @Override 1289 public void invokePaymentApp(String id, String merchantName, String origin, 1290 String iframeOrigin, byte[][] certificateChain, 1291 Map<String, PaymentMethodData> methodData, PaymentItem total, 1292 List<PaymentItem> displayItems, Map<String, PaymentDetailsModifier> modifiers, 1293 PaymentOptions paymentOptions, List<PaymentShippingOption> shippingOptions, 1294 InstrumentDetailsCallback detailsCallback) { 1295 Runnable respond = () -> { 1296 detailsCallback.onInstrumentDetailsReady(mDefaultMethodName, 1297 "{\"transaction\": 1337, \"total\": \"" + total.amount.value + "\"}", 1298 new PayerData()); 1299 }; 1300 if (mAppSpeed == AppSpeed.FAST_APP) { 1301 respond.run(); 1302 } else { 1303 new Handler().postDelayed(respond, 100); 1304 } 1305 } 1306 1307 @Override 1308 public void dismissInstrument() {} 1309 } 1310 1311 public void onMainActivityStarted() throws TimeoutException { 1312 if (mCallback != null) { 1313 mCallback.onMainActivityStarted(); 1314 } 1315 } 1316 1317 @Override 1318 public Statement apply(final Statement base, Description description) { 1319 return super.apply(new Statement() { 1320 @Override 1321 public void evaluate() throws Throwable { 1322 if (!mDelayStartActivity) startMainActivity(); 1323 base.evaluate(); 1324 } 1325 }, description); 1326 } 1327 1328 /** The interface for being notified of the main activity startup. */ 1329 public interface MainActivityStartCallback { 1330 /** Called when the main activity has started up. */ 1331 void onMainActivityStarted() throws TimeoutException; 1332 } 1333 } 1334