Commit 64d522c2 authored by Rouslan Solomakhin's avatar Rouslan Solomakhin Committed by Commit Bot

[Web Payment] Native Android payment app factory.

This patch adds a payment app factory for 3rd party native Android
payment apps in accordance with the design at:
https://bit.ly/cross-platform-pay-app-factory

The factory is responsible for finding the payment apps
(AndroidPaymentApp), filtering out apps that don't support the requested
methods, verifying ability to use the method names, and querying the
IS_READY_TO_PAY service when necessary.

Bug: 1022512
Change-Id: Id3862e8a7e1aa6ace6ce70adde56cd90c20c23fa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2025229Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarSahel Sharify <sahel@chromium.org>
Commit-Queue: Rouslan Solomakhin <rouslan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#739031}
parent 9457a610
...@@ -16,6 +16,7 @@ import android.os.Handler; ...@@ -16,6 +16,7 @@ import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Parcelable; import android.os.Parcelable;
import android.os.RemoteException; import android.os.RemoteException;
import android.support.annotation.VisibleForTesting;
import android.util.JsonWriter; import android.util.JsonWriter;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
...@@ -38,7 +39,6 @@ import org.chromium.payments.mojom.PaymentOptions; ...@@ -38,7 +39,6 @@ import org.chromium.payments.mojom.PaymentOptions;
import org.chromium.payments.mojom.PaymentShippingOption; import org.chromium.payments.mojom.PaymentShippingOption;
import org.chromium.ui.UiUtils; import org.chromium.ui.UiUtils;
import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.base.WindowAndroid;
import org.chromium.url.URI;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
...@@ -53,10 +53,9 @@ import java.util.Set; ...@@ -53,10 +53,9 @@ import java.util.Set;
/** /**
* The point of interaction with a locally installed 3rd party native Android payment app. * The point of interaction with a locally installed 3rd party native Android payment app.
* https://docs.google.com/document/d/1izV4uC-tiRJG3JLooqY3YRLU22tYOsLTNq0P_InPJeE * https://developers.google.com/web/fundamentals/payments/payment-apps-developer-guide/android-payment-apps
*/ */
public class AndroidPaymentApp public class AndroidPaymentApp extends PaymentInstrument implements WindowAndroid.IntentCallback {
extends PaymentInstrument implements PaymentApp, WindowAndroid.IntentCallback {
/** The action name for the Pay Intent. */ /** The action name for the Pay Intent. */
public static final String ACTION_PAY = "org.chromium.intent.action.PAY"; public static final String ACTION_PAY = "org.chromium.intent.action.PAY";
...@@ -97,45 +96,54 @@ public class AndroidPaymentApp ...@@ -97,45 +96,54 @@ public class AndroidPaymentApp
private final Handler mHandler; private final Handler mHandler;
private final WebContents mWebContents; private final WebContents mWebContents;
private final Intent mIsReadyToPayIntent;
private final Intent mPayIntent; private final Intent mPayIntent;
private final Set<String> mMethodNames; private final Set<String> mMethodNames;
private final boolean mIsIncognito; private final boolean mIsIncognito;
private InstrumentsCallback mInstrumentsCallback; private Intent mIsReadyToPayIntent;
private IsReadyToPayCallback mIsReadyToPayCallback;
private InstrumentDetailsCallback mInstrumentDetailsCallback; private InstrumentDetailsCallback mInstrumentDetailsCallback;
private ServiceConnection mServiceConnection; private ServiceConnection mServiceConnection;
@Nullable @Nullable
private URI mCanDedupedApplicationId; private String mApplicationIdentifierToHide;
private boolean mIsReadyToPayQueried; private boolean mIsReadyToPayQueried;
private boolean mIsServiceBindingInitiated; private boolean mIsServiceBindingInitiated;
private boolean mBypassIsReadyToPayServiceInTest;
/** /**
* Builds the point of interaction with a locally installed 3rd party native Android payment * Builds the point of interaction with a locally installed 3rd party native Android payment
* app. * app.
* *
* @param webContents The web contents. * @param webContents The web contents.
* @param packageName The name of the package of the payment app. * @param packageName The name of the package of the payment app.
* @param activity The name of the payment activity in the payment app. * @param activity The name of the payment activity in the payment app.
* @param label The UI label to use for the payment app. * @param isReadyToPayService The name of the service that can answer "is ready to pay" query,
* @param icon The icon to use in UI for the payment app. * or null of none.
* @param isIncognito Whether the user is in incognito mode. * @param label The UI label to use for the payment app.
* @param canDedupedApplicationId The corresponding app Id this app can deduped. * @param icon The icon to use in UI for the payment app.
* @param isIncognito Whether the user is in incognito mode.
* @param appToHide The identifier of the application that this app can hide.
*/ */
public AndroidPaymentApp(WebContents webContents, String packageName, String activity, public AndroidPaymentApp(WebContents webContents, String packageName, String activity,
String label, Drawable icon, boolean isIncognito, @Nullable String isReadyToPayService, String label, Drawable icon, boolean isIncognito,
@Nullable URI canDedupedApplicationId) { @Nullable String appToHide) {
super(packageName, label, null, icon); super(packageName, label, null, icon);
ThreadUtils.assertOnUiThread(); ThreadUtils.assertOnUiThread();
mHandler = new Handler(); mHandler = new Handler();
mWebContents = webContents; mWebContents = webContents;
mPayIntent = new Intent(); mPayIntent = new Intent();
mIsReadyToPayIntent = new Intent();
mIsReadyToPayIntent.setPackage(packageName);
mPayIntent.setClassName(packageName, activity); mPayIntent.setClassName(packageName, activity);
mPayIntent.setAction(ACTION_PAY); mPayIntent.setAction(ACTION_PAY);
if (isReadyToPayService != null) {
assert !isIncognito;
mIsReadyToPayIntent = new Intent();
mIsReadyToPayIntent.setClassName(packageName, isReadyToPayService);
}
mMethodNames = new HashSet<>(); mMethodNames = new HashSet<>();
mIsIncognito = isIncognito; mIsIncognito = isIncognito;
mCanDedupedApplicationId = canDedupedApplicationId; mApplicationIdentifierToHide = appToHide;
} }
/** @param methodName A payment method that this app supports, e.g., "https://bobpay.com". */ /** @param methodName A payment method that this app supports, e.g., "https://bobpay.com". */
...@@ -143,22 +151,27 @@ public class AndroidPaymentApp ...@@ -143,22 +151,27 @@ public class AndroidPaymentApp
mMethodNames.add(methodName); mMethodNames.add(methodName);
} }
/** @param className The class name of the "is ready to pay" service in the payment app. */ /** Callback for receiving responses to IS_READY_TO_PAY queries. */
public void setIsReadyToPayAction(String className) { /* package */ interface IsReadyToPayCallback {
mIsReadyToPayIntent.setClassName(mIsReadyToPayIntent.getPackage(), className); /**
* Called after it is known whether the given app is ready to pay.
* @param app The app that has been queried.
* @param isReadyToPay Whether the app is ready to pay.
*/
void onIsReadyToPayResponse(AndroidPaymentApp app, boolean isReadyToPay);
} }
@Override /** Queries the IS_READY_TO_PAY service. */
public void getInstruments(String unusedId, Map<String, PaymentMethodData> methodDataMap, /* package */ void maybeQueryIsReadyToPayService(Map<String, PaymentMethodData> methodDataMap,
String origin, String iframeOrigin, @Nullable byte[][] certificateChain, String origin, String iframeOrigin, @Nullable byte[][] certificateChain,
Map<String, PaymentDetailsModifier> modifiers, InstrumentsCallback callback) { Map<String, PaymentDetailsModifier> modifiers, IsReadyToPayCallback callback) {
assert mMethodNames.containsAll(methodDataMap.keySet()); assert mMethodNames.containsAll(methodDataMap.keySet());
assert mInstrumentsCallback assert mIsReadyToPayCallback
== null : "Have not responded to previous request for instruments yet"; == null : "Have not responded to previous request for instruments yet";
mInstrumentsCallback = callback; mIsReadyToPayCallback = callback;
if (mIsReadyToPayIntent.getComponent() == null) { if (mIsReadyToPayIntent == null) {
respondToGetInstrumentsQuery(AndroidPaymentApp.this); respondToIsReadyToPayQuery(true);
return; return;
} }
...@@ -169,7 +182,7 @@ public class AndroidPaymentApp ...@@ -169,7 +182,7 @@ public class AndroidPaymentApp
IsReadyToPayService isReadyToPayService = IsReadyToPayService isReadyToPayService =
IsReadyToPayService.Stub.asInterface(service); IsReadyToPayService.Stub.asInterface(service);
if (isReadyToPayService == null) { if (isReadyToPayService == null) {
respondToGetInstrumentsQuery(null); respondToIsReadyToPayQuery(false);
} else { } else {
sendIsReadyToPayIntentToPaymentApp(isReadyToPayService); sendIsReadyToPayIntentToPaymentApp(isReadyToPayService);
} }
...@@ -184,13 +197,19 @@ public class AndroidPaymentApp ...@@ -184,13 +197,19 @@ public class AndroidPaymentApp
@Override @Override
public void onServiceDisconnected(ComponentName name) { public void onServiceDisconnected(ComponentName name) {
// Do not wait for the service to restart. // Do not wait for the service to restart.
respondToGetInstrumentsQuery(null); respondToIsReadyToPayQuery(false);
} }
}; };
mIsReadyToPayIntent.putExtras(buildExtras(null /* id */, null /* merchantName */, mIsReadyToPayIntent.putExtras(buildExtras(null /* id */, null /* merchantName */,
removeUrlScheme(origin), removeUrlScheme(iframeOrigin), certificateChain, removeUrlScheme(origin), removeUrlScheme(iframeOrigin), certificateChain,
methodDataMap, null /* total */, null /* displayItems */, null /* modifiers */)); methodDataMap, null /* total */, null /* displayItems */, null /* modifiers */));
if (mBypassIsReadyToPayServiceInTest) {
respondToIsReadyToPayQuery(true);
return;
}
try { try {
// This method returns "true if the system is in the process of bringing up a service // This method returns "true if the system is in the process of bringing up a service
// that your client has permission to bind to; false if the system couldn't find the // that your client has permission to bind to; false if the system couldn't find the
...@@ -205,16 +224,21 @@ public class AndroidPaymentApp ...@@ -205,16 +224,21 @@ public class AndroidPaymentApp
} }
if (!mIsServiceBindingInitiated) { if (!mIsServiceBindingInitiated) {
respondToGetInstrumentsQuery(null); respondToIsReadyToPayQuery(false);
return; return;
} }
mHandler.postDelayed(() -> { mHandler.postDelayed(() -> {
if (!mIsReadyToPayQueried) respondToGetInstrumentsQuery(null); if (!mIsReadyToPayQueried) respondToIsReadyToPayQuery(false);
}, SERVICE_CONNECTION_TIMEOUT_MS); }, SERVICE_CONNECTION_TIMEOUT_MS);
} }
private void respondToGetInstrumentsQuery(final PaymentInstrument instrument) { @VisibleForTesting
/* package */ void bypassIsReadyToPayServiceInTest() {
mBypassIsReadyToPayServiceInTest = true;
}
private void respondToIsReadyToPayQuery(boolean isReadyToPay) {
if (mServiceConnection != null) { if (mServiceConnection != null) {
if (mIsServiceBindingInitiated) { if (mIsServiceBindingInitiated) {
// mServiceConnection "parameter must not be null." // mServiceConnection "parameter must not be null."
...@@ -225,31 +249,18 @@ public class AndroidPaymentApp ...@@ -225,31 +249,18 @@ public class AndroidPaymentApp
mServiceConnection = null; mServiceConnection = null;
} }
if (mInstrumentsCallback == null) return; if (mIsReadyToPayCallback == null) return;
mHandler.post(() -> { mIsReadyToPayCallback.onIsReadyToPayResponse(/*app=*/this, isReadyToPay);
ThreadUtils.assertOnUiThread(); mIsReadyToPayCallback = null;
if (mInstrumentsCallback == null) return;
List<PaymentInstrument> instruments = null;
if (instrument != null) {
instruments = new ArrayList<>();
instruments.add(instrument);
}
mInstrumentsCallback.onInstrumentsReady(AndroidPaymentApp.this, instruments);
mInstrumentsCallback = null;
});
} }
private void sendIsReadyToPayIntentToPaymentApp(IsReadyToPayService isReadyToPayService) { private void sendIsReadyToPayIntentToPaymentApp(IsReadyToPayService isReadyToPayService) {
if (mInstrumentsCallback == null) return; if (mIsReadyToPayCallback == null) return;
mIsReadyToPayQueried = true; mIsReadyToPayQueried = true;
IsReadyToPayServiceCallback.Stub callback = new IsReadyToPayServiceCallback.Stub() { IsReadyToPayServiceCallback.Stub callback = new IsReadyToPayServiceCallback.Stub() {
@Override @Override
public void handleIsReadyToPay(boolean isReadyToPay) throws RemoteException { public void handleIsReadyToPay(boolean isReadyToPay) throws RemoteException {
if (isReadyToPay) { respondToIsReadyToPayQuery(isReadyToPay);
respondToGetInstrumentsQuery(AndroidPaymentApp.this);
} else {
respondToGetInstrumentsQuery(null);
}
} }
}; };
try { try {
...@@ -257,38 +268,21 @@ public class AndroidPaymentApp ...@@ -257,38 +268,21 @@ public class AndroidPaymentApp
} catch (Throwable e) { } catch (Throwable e) {
// Many undocumented exceptions are not caught in the remote Service but passed on to // Many undocumented exceptions are not caught in the remote Service but passed on to
// the Service caller, see writeException in Parcel.java. // the Service caller, see writeException in Parcel.java.
respondToGetInstrumentsQuery(null); respondToIsReadyToPayQuery(false);
return; return;
} }
mHandler.postDelayed(() -> respondToGetInstrumentsQuery(null), READY_TO_PAY_TIMEOUT_MS); mHandler.postDelayed(() -> respondToIsReadyToPayQuery(false), READY_TO_PAY_TIMEOUT_MS);
} }
@Override @Override
public boolean supportsMethodsAndData(Map<String, PaymentMethodData> methodsAndData) { @Nullable
assert methodsAndData != null; public String getApplicationIdentifierToHide() {
Set<String> methodNames = new HashSet<>(methodsAndData.keySet()); return mApplicationIdentifierToHide;
methodNames.retainAll(getAppMethodNames());
return !methodNames.isEmpty();
}
@Override
public URI getCanDedupedApplicationId() {
return mCanDedupedApplicationId;
}
@Override
public String getAppIdentifier() {
return getIdentifier();
}
@Override
public Set<String> getAppMethodNames() {
return Collections.unmodifiableSet(mMethodNames);
} }
@Override @Override
public Set<String> getInstrumentMethodNames() { public Set<String> getInstrumentMethodNames() {
return getAppMethodNames(); return Collections.unmodifiableSet(mMethodNames);
} }
@Override @Override
...@@ -502,8 +496,7 @@ public class AndroidPaymentApp ...@@ -502,8 +496,7 @@ public class AndroidPaymentApp
return stringWriter.toString(); return stringWriter.toString();
} }
private static void serializeTotal(PaymentItem item, JsonWriter json) private static void serializeTotal(PaymentItem item, JsonWriter json) throws IOException {
throws IOException {
// item {{{ // item {{{
json.beginObject(); json.beginObject();
// Sanitize the total name, because the payment app does not need it to complete the // Sanitize the total name, because the payment app does not need it to complete the
......
...@@ -10,26 +10,23 @@ import android.graphics.drawable.Drawable; ...@@ -10,26 +10,23 @@ import android.graphics.drawable.Drawable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair; import android.util.Pair;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedCallback;
import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppFactoryAddition;
import org.chromium.components.payments.PaymentManifestDownloader; import org.chromium.components.payments.PaymentManifestDownloader;
import org.chromium.components.payments.PaymentManifestParser; import org.chromium.components.payments.PaymentManifestParser;
import org.chromium.content_public.browser.WebContents;
import org.chromium.payments.mojom.PaymentMethodData;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/** Builds instances of payment apps based on installed third party Android payment apps. */ /** Looks up installed third party Android payment apps. */
public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition { public class AndroidPaymentAppFactory implements PaymentAppFactoryInterface {
// PaymentAppFactoryInterface implementation.
@Override @Override
public void create(WebContents webContents, Map<String, PaymentMethodData> methodData, public void create(PaymentAppFactoryDelegate delegate) {
boolean mayCrawlUnused, PaymentAppCreatedCallback callback) { AndroidPaymentAppFinder finder =
AndroidPaymentAppFinder.find(webContents, methodData.keySet(), new AndroidPaymentAppFinder(new PaymentManifestWebDataService(),
new PaymentManifestWebDataService(), new PaymentManifestDownloader(), new PaymentManifestDownloader(), new PaymentManifestParser(),
new PaymentManifestParser(), new PackageManagerDelegate(), callback); new PackageManagerDelegate(), delegate, /*factory=*/this);
finder.findAndroidPaymentApps();
} }
/** /**
...@@ -38,8 +35,6 @@ public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition { ...@@ -38,8 +35,6 @@ public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition {
* @return True if there are Android payment apps on device. * @return True if there are Android payment apps on device.
*/ */
public static boolean hasAndroidPaymentApps() { public static boolean hasAndroidPaymentApps() {
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.ANDROID_PAYMENT_APPS)) return false;
PackageManagerDelegate packageManagerDelegate = new PackageManagerDelegate(); PackageManagerDelegate packageManagerDelegate = new PackageManagerDelegate();
// Note that all Android payment apps must support org.chromium.intent.action.PAY action // Note that all Android payment apps must support org.chromium.intent.action.PAY action
// without additional data to be detected. // without additional data to be detected.
...@@ -56,10 +51,6 @@ public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition { ...@@ -56,10 +51,6 @@ public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition {
public static Map<String, Pair<String, Drawable>> getAndroidPaymentAppsInfo() { public static Map<String, Pair<String, Drawable>> getAndroidPaymentAppsInfo() {
Map<String, Pair<String, Drawable>> paymentAppsInfo = new HashMap<>(); Map<String, Pair<String, Drawable>> paymentAppsInfo = new HashMap<>();
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.ANDROID_PAYMENT_APPS)) {
return paymentAppsInfo;
}
PackageManagerDelegate packageManagerDelegate = new PackageManagerDelegate(); PackageManagerDelegate packageManagerDelegate = new PackageManagerDelegate();
Intent payIntent = new Intent(AndroidPaymentApp.ACTION_PAY); Intent payIntent = new Intent(AndroidPaymentApp.ACTION_PAY);
List<ResolveInfo> matches = List<ResolveInfo> matches =
......
...@@ -7,16 +7,20 @@ package org.chromium.chrome.browser.payments; ...@@ -7,16 +7,20 @@ package org.chromium.chrome.browser.payments;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.Nullable;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.chrome.browser.ChromeActivity; import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedCallback;
import org.chromium.chrome.browser.payments.PaymentManifestVerifier.ManifestVerifyCallback; import org.chromium.chrome.browser.payments.PaymentManifestVerifier.ManifestVerifyCallback;
import org.chromium.components.payments.MethodStrings; import org.chromium.components.payments.MethodStrings;
import org.chromium.components.payments.PaymentManifestDownloader; import org.chromium.components.payments.PaymentManifestDownloader;
import org.chromium.components.payments.PaymentManifestParser; import org.chromium.components.payments.PaymentManifestParser;
import org.chromium.content_public.browser.WebContents; import org.chromium.payments.mojom.PaymentDetailsModifier;
import org.chromium.payments.mojom.PaymentMethodData;
import org.chromium.url.URI; import org.chromium.url.URI;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -55,19 +59,19 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -55,19 +59,19 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
/* package */ static final String META_DATA_NAME_OF_DEFAULT_PAYMENT_METHOD_NAME = /* package */ static final String META_DATA_NAME_OF_DEFAULT_PAYMENT_METHOD_NAME =
"org.chromium.default_payment_method_name"; "org.chromium.default_payment_method_name";
private final WebContents mWebContents;
private final Set<String> mNonUriPaymentMethods; private final Set<String> mNonUriPaymentMethods;
private final Set<URI> mUriPaymentMethods; private final Set<URI> mUriPaymentMethods;
private final PaymentManifestDownloader mDownloader; private final PaymentManifestDownloader mDownloader;
private final PaymentManifestWebDataService mWebDataService; private final PaymentManifestWebDataService mWebDataService;
private final PaymentManifestParser mParser; private final PaymentManifestParser mParser;
private final PackageManagerDelegate mPackageManagerDelegate; private final PackageManagerDelegate mPackageManagerDelegate;
private final PaymentAppCreatedCallback mCallback; private final PaymentAppFactoryDelegate mDelegate;
private final PaymentAppFactoryInterface mFactory;
private final boolean mIsIncognito; private final boolean mIsIncognito;
/** /**
* A mapping from an Android package name to the payment app with that package name. The apps * A mapping from an Android package name to the payment app with that package name. The apps
* will be sent to the <code>PaymentAppCreatedCallback</code> once all of their payment methods * will be sent to the <code>PaymentAppFactoryDelegate</code> once all of their payment methods
* have been validated. The package names are used for identification because they are unique on * have been validated. The package names are used for identification because they are unique on
* Android. Example contents: * Android. Example contents:
* *
...@@ -116,37 +120,36 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -116,37 +120,36 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
*/ */
private final Map<URI, PaymentMethod> mVerifiedPaymentMethods = new HashMap<>(); private final Map<URI, PaymentMethod> mVerifiedPaymentMethods = new HashMap<>();
/*
* A mapping from package names to their IS_READY_TO_PAY service names, e.g.:
*
* {"com.bobpay.app": "com.bobpay.app.IsReadyToPayService"}
*/
private final Map<String, String> mIsReadyToPayServices = new HashMap<>();
private int mPendingVerifiersCount; private int mPendingVerifiersCount;
private int mPendingIsReadyToPayQueries;
private int mPendingResourceUsersCount; private int mPendingResourceUsersCount;
private boolean mBypassIsReadyToPayServiceInTest;
/** /**
* Finds native Android payment apps. * Builds a finder for native Android payment apps.
* *
* @param webContents The web contents that invoked the web payments API.
* @param methods The list of payment methods requested by the merchant. For
* example, "https://bobpay.com", "https://android.com/pay",
* "basic-card".
* @param webDataService The web data service to cache manifest. * @param webDataService The web data service to cache manifest.
* @param downloader The manifest downloader. * @param downloader The manifest downloader.
* @param parser The manifest parser. * @param parser The manifest parser.
* @param packageManagerDelegate The package information retriever. * @param packageManagerDelegate The package information retriever.
* @param callback The asynchronous callback to be invoked (on the UI thread) when * @param delegate The merchant requested data and the asynchronous delegate to be
* all Android payment apps have been found. * invoked (on the UI thread) when all Android payment apps have
* been found.
* @param factory The factory to be used in the
* delegate.onDoneCreatingPaymentApps(factory) call.
*/ */
public static void find(WebContents webContents, Set<String> methods, /* package */ AndroidPaymentAppFinder(PaymentManifestWebDataService webDataService,
PaymentManifestWebDataService webDataService, PaymentManifestDownloader downloader, PaymentManifestDownloader downloader, PaymentManifestParser parser,
PaymentManifestParser parser, PackageManagerDelegate packageManagerDelegate, PackageManagerDelegate packageManagerDelegate, PaymentAppFactoryDelegate delegate,
PaymentAppCreatedCallback callback) { PaymentAppFactoryInterface factory) {
new AndroidPaymentAppFinder(webContents, methods, webDataService, downloader, parser, mDelegate = delegate;
packageManagerDelegate, callback)
.findAndroidPaymentApps();
}
private AndroidPaymentAppFinder(WebContents webContents, Set<String> methods,
PaymentManifestWebDataService webDataService, PaymentManifestDownloader downloader,
PaymentManifestParser parser, PackageManagerDelegate packageManagerDelegate,
PaymentAppCreatedCallback callback) {
mWebContents = webContents;
// For non-URI payment method names, only names published by W3C should be supported. Keep // For non-URI payment method names, only names published by W3C should be supported. Keep
// this in sync with manifest_verifier.cc. // this in sync with manifest_verifier.cc.
...@@ -159,7 +162,7 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -159,7 +162,7 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
mNonUriPaymentMethods = new HashSet<>(); mNonUriPaymentMethods = new HashSet<>();
mUriPaymentMethods = new HashSet<>(); mUriPaymentMethods = new HashSet<>();
for (String method : methods) { for (String method : mDelegate.getParams().getMethodData().keySet()) {
assert !TextUtils.isEmpty(method); assert !TextUtils.isEmpty(method);
if (supportedNonUriPaymentMethods.contains(method)) { if (supportedNonUriPaymentMethods.contains(method)) {
mNonUriPaymentMethods.add(method); mNonUriPaymentMethods.add(method);
...@@ -173,8 +176,9 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -173,8 +176,9 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
mWebDataService = webDataService; mWebDataService = webDataService;
mParser = parser; mParser = parser;
mPackageManagerDelegate = packageManagerDelegate; mPackageManagerDelegate = packageManagerDelegate;
mCallback = callback; mFactory = factory;
ChromeActivity activity = ChromeActivity.fromWebContents(mWebContents); ChromeActivity activity =
ChromeActivity.fromWebContents(mDelegate.getParams().getWebContents());
mIsIncognito = activity != null && activity.getCurrentTabModel().isIncognito(); mIsIncognito = activity != null && activity.getCurrentTabModel().isIncognito();
} }
...@@ -182,15 +186,25 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -182,15 +186,25 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
* Finds and validates the installed android payment apps that support the payment method names * Finds and validates the installed android payment apps that support the payment method names
* that the merchant is using. * that the merchant is using.
*/ */
private void findAndroidPaymentApps() { /* package */ void findAndroidPaymentApps() {
List<ResolveInfo> allInstalledPaymentApps = List<ResolveInfo> allInstalledPaymentApps =
mPackageManagerDelegate.getActivitiesThatCanRespondToIntentWithMetaData( mPackageManagerDelegate.getActivitiesThatCanRespondToIntentWithMetaData(
new Intent(AndroidPaymentApp.ACTION_PAY)); new Intent(AndroidPaymentApp.ACTION_PAY));
if (allInstalledPaymentApps.isEmpty()) { if (allInstalledPaymentApps.isEmpty()) {
onAllAppsFound(); onAllAppsFoundAndValidated();
return; return;
} }
if (!mIsIncognito) {
List<ResolveInfo> services = mPackageManagerDelegate.getServicesThatCanRespondToIntent(
new Intent(ACTION_IS_READY_TO_PAY));
int numberOfServices = services.size();
for (int i = 0; i < numberOfServices; i++) {
ServiceInfo service = services.get(i).serviceInfo;
mIsReadyToPayServices.put(service.packageName, service.name);
}
}
// All URI methods for which manifests should be downloaded. For example, if merchant // All URI methods for which manifests should be downloaded. For example, if merchant
// supports "https://bobpay.com/personal" payment method, but user also has Alice Pay app // supports "https://bobpay.com/personal" payment method, but user also has Alice Pay app
// that has the default payment method name of "https://alicepay.com/webpay" that claims to // that has the default payment method name of "https://alicepay.com/webpay" that claims to
...@@ -232,14 +246,16 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -232,14 +246,16 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
String defaultMethod = app.activityInfo.metaData == null String defaultMethod = app.activityInfo.metaData == null
? null ? null
: app.activityInfo.metaData.getString( : app.activityInfo.metaData.getString(
META_DATA_NAME_OF_DEFAULT_PAYMENT_METHOD_NAME); META_DATA_NAME_OF_DEFAULT_PAYMENT_METHOD_NAME);
URI appOrigin = null; URI appOrigin = null;
URI defaultUriMethod = null; URI defaultUriMethod = null;
if (!TextUtils.isEmpty(defaultMethod)) { if (!TextUtils.isEmpty(defaultMethod)) {
if (UriUtils.looksLikeUriMethod(defaultMethod)) { if (UriUtils.looksLikeUriMethod(defaultMethod)) {
defaultUriMethod = UriUtils.parseUriFromString(defaultMethod); defaultUriMethod = UriUtils.parseUriFromString(defaultMethod);
if (defaultUriMethod != null) defaultMethod = defaultUriMethod.toString(); if (defaultUriMethod != null) {
defaultMethod = uriToStringWithoutTrailingSlash(defaultUriMethod);
}
} }
if (!methodToAppsMapping.containsKey(defaultMethod)) { if (!methodToAppsMapping.containsKey(defaultMethod)) {
methodToAppsMapping.put(defaultMethod, new HashSet<ResolveInfo>()); methodToAppsMapping.put(defaultMethod, new HashSet<ResolveInfo>());
...@@ -298,13 +314,19 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -298,13 +314,19 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
List<PaymentManifestVerifier> manifestVerifiers = new ArrayList<>(); List<PaymentManifestVerifier> manifestVerifiers = new ArrayList<>();
for (URI uriMethodName : uriMethods) { for (URI uriMethodName : uriMethods) {
if (!methodToAppsMapping.containsKey(uriMethodName.toString())) continue; if (!methodToAppsMapping.containsKey(uriToStringWithoutTrailingSlash(uriMethodName))) {
continue;
}
if (!mParser.isNativeInitialized()) mParser.createNative(mWebContents); if (!mParser.isNativeInitialized()) {
mParser.createNative(mDelegate.getParams().getWebContents());
}
// Initialize the native side of the downloader, once we know that a manifest file needs // Initialize the native side of the downloader, once we know that a manifest file needs
// to be downloaded. // to be downloaded.
if (!mDownloader.isInitialized()) mDownloader.initialize(mWebContents); if (!mDownloader.isInitialized()) {
mDownloader.initialize(mDelegate.getParams().getWebContents());
}
manifestVerifiers.add(new PaymentManifestVerifier(uriMethodName, manifestVerifiers.add(new PaymentManifestVerifier(uriMethodName,
uriMethodToDefaultAppsMapping.get(uriMethodName), uriMethodToDefaultAppsMapping.get(uriMethodName),
...@@ -328,7 +350,7 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -328,7 +350,7 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
} }
if (manifestVerifiers.isEmpty()) { if (manifestVerifiers.isEmpty()) {
onAllAppsFound(); onAllAppsFoundAndValidated();
return; return;
} }
...@@ -365,7 +387,7 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -365,7 +387,7 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
if (UriUtils.looksLikeUriMethod(method)) { if (UriUtils.looksLikeUriMethod(method)) {
uriMethod = UriUtils.parseUriFromString(method); uriMethod = UriUtils.parseUriFromString(method);
} }
result.add(uriMethod != null ? uriMethod.toString() : method); result.add(uriMethod != null ? uriToStringWithoutTrailingSlash(uriMethod) : method);
} }
return result; return result;
...@@ -397,7 +419,7 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -397,7 +419,7 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
@Override @Override
public void onVerificationError(String errorMessage) { public void onVerificationError(String errorMessage) {
mCallback.onGetPaymentAppsError(errorMessage); mDelegate.onPaymentAppCreationError(errorMessage);
} }
@Override @Override
...@@ -410,8 +432,9 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -410,8 +432,9 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
if (!mUriPaymentMethods.contains(methodName)) continue; if (!mUriPaymentMethods.contains(methodName)) continue;
PaymentMethod method = nameAndMethod.getValue(); PaymentMethod method = nameAndMethod.getValue();
String methodNameString = uriToStringWithoutTrailingSlash(methodName);
for (ResolveInfo app : method.defaultApplications) { for (ResolveInfo app : method.defaultApplications) {
onValidPaymentAppForPaymentMethodName(app, methodName.toString()); onValidPaymentAppForPaymentMethodName(app, methodNameString);
} }
// Chrome does not verify payment apps if they claim to support URI payment methods // Chrome does not verify payment apps if they claim to support URI payment methods
...@@ -420,7 +443,7 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -420,7 +443,7 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
Set<ResolveInfo> supportedApps = mMethodToSupportedAppsMapping.get(methodName); Set<ResolveInfo> supportedApps = mMethodToSupportedAppsMapping.get(methodName);
if (supportedApps == null) continue; if (supportedApps == null) continue;
for (ResolveInfo supportedApp : supportedApps) { for (ResolveInfo supportedApp : supportedApps) {
onValidPaymentAppForPaymentMethodName(supportedApp, methodName.toString()); onValidPaymentAppForPaymentMethodName(supportedApp, methodNameString);
} }
continue; continue;
} }
...@@ -436,35 +459,77 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -436,35 +459,77 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
if (supportedAppMethod == null) continue; if (supportedAppMethod == null) continue;
for (ResolveInfo supportedApp : supportedAppMethod.defaultApplications) { for (ResolveInfo supportedApp : supportedAppMethod.defaultApplications) {
onValidPaymentAppForPaymentMethodName(supportedApp, methodName.toString()); onValidPaymentAppForPaymentMethodName(supportedApp, methodNameString);
} }
} }
} }
} }
onAllAppsFound(); onAllAppsFoundAndValidated();
} }
/** Notifies callback that all payment apps have been found. */ /**
private void onAllAppsFound() { * Queries the IS_READY_TO_PAY service of all valid payment apps. Only valid payment apps
* receive IS_READY_TO_PAY query to avoid exposing browsing history to malicious apps.
*
* Must be done after all verifiers have finished because some manifest files may validate
* multiple apps and some apps may require multiple manifest file for verification.
*/
private void onAllAppsFoundAndValidated() {
assert mPendingVerifiersCount == 0; assert mPendingVerifiersCount == 0;
if (!mIsIncognito) { mDelegate.onCanMakePaymentCalculated(mValidApps.size() > 0);
List<ResolveInfo> resolveInfos = if (mValidApps.isEmpty()) {
mPackageManagerDelegate.getServicesThatCanRespondToIntent( mDelegate.onDoneCreatingPaymentApps(mFactory);
new Intent(ACTION_IS_READY_TO_PAY)); return;
for (int i = 0; i < resolveInfos.size(); i++) {
ResolveInfo resolveInfo = resolveInfos.get(i);
AndroidPaymentApp app = mValidApps.get(resolveInfo.serviceInfo.packageName);
if (app != null) app.setIsReadyToPayAction(resolveInfo.serviceInfo.name);
}
} }
mPendingIsReadyToPayQueries = mValidApps.size();
for (Map.Entry<String, AndroidPaymentApp> entry : mValidApps.entrySet()) { for (Map.Entry<String, AndroidPaymentApp> entry : mValidApps.entrySet()) {
mCallback.onPaymentAppCreated(entry.getValue()); AndroidPaymentApp app = entry.getValue();
if (mBypassIsReadyToPayServiceInTest) app.bypassIsReadyToPayServiceInTest();
app.maybeQueryIsReadyToPayService(
filterMethodDataForApp(
mDelegate.getParams().getMethodData(), app.getInstrumentMethodNames()),
mDelegate.getParams().getTopLevelOrigin(),
mDelegate.getParams().getPaymentRequestOrigin(),
mDelegate.getParams().getCertificateChain(),
filterModifiersForApp(
mDelegate.getParams().getModifiers(), app.getInstrumentMethodNames()),
this::onIsReadyToPayResponse);
}
}
@VisibleForTesting
/* package */ void bypassIsReadyToPayServiceInTest() {
mBypassIsReadyToPayServiceInTest = true;
}
private static Map<String, PaymentMethodData> filterMethodDataForApp(
Map<String, PaymentMethodData> methodData, Set<String> appMethodNames) {
Map<String, PaymentMethodData> filtered = new HashMap<>();
for (String methodName : appMethodNames) {
if (methodData.containsKey(methodName)) {
filtered.put(methodName, methodData.get(methodName));
}
} }
return filtered;
}
private static Map<String, PaymentDetailsModifier> filterModifiersForApp(
Map<String, PaymentDetailsModifier> modifiers, Set<String> appMethodNames) {
Map<String, PaymentDetailsModifier> filtered = new HashMap<>();
for (String methodName : appMethodNames) {
if (modifiers.containsKey(methodName)) {
filtered.put(methodName, modifiers.get(methodName));
}
}
return filtered;
}
mCallback.onAllPaymentAppsCreated(); private void onIsReadyToPayResponse(AndroidPaymentApp app, boolean isReadyToPay) {
if (isReadyToPay) mDelegate.onPaymentAppCreated(app);
if (--mPendingIsReadyToPayQueries == 0) mDelegate.onDoneCreatingPaymentApps(mFactory);
} }
/** /**
...@@ -488,11 +553,11 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -488,11 +553,11 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
String webAppIdCanDeduped = resolveInfo.activityInfo.metaData == null String webAppIdCanDeduped = resolveInfo.activityInfo.metaData == null
? null ? null
: resolveInfo.activityInfo.metaData.getString( : resolveInfo.activityInfo.metaData.getString(
META_DATA_NAME_OF_DEFAULT_PAYMENT_METHOD_NAME); META_DATA_NAME_OF_DEFAULT_PAYMENT_METHOD_NAME);
app = new AndroidPaymentApp(mWebContents, packageName, resolveInfo.activityInfo.name, app = new AndroidPaymentApp(mDelegate.getParams().getWebContents(), packageName,
resolveInfo.activityInfo.name, mIsReadyToPayServices.get(packageName),
label.toString(), mPackageManagerDelegate.getAppIcon(resolveInfo), mIsIncognito, label.toString(), mPackageManagerDelegate.getAppIcon(resolveInfo), mIsIncognito,
webAppIdCanDeduped == null ? null webAppIdCanDeduped);
: UriUtils.parseUriFromString(webAppIdCanDeduped));
mValidApps.put(packageName, app); mValidApps.put(packageName, app);
} }
...@@ -509,4 +574,20 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback { ...@@ -509,4 +574,20 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
if (mDownloader.isInitialized()) mDownloader.destroy(); if (mDownloader.isInitialized()) mDownloader.destroy();
if (mParser.isNativeInitialized()) mParser.destroyNative(); if (mParser.isNativeInitialized()) mParser.destroyNative();
} }
/**
* Converts the given URI to a string without a trailing slash, because payment method
* identifiers typically omit trailing slashes, e.g., "https://google.com/pay" is correct,
* whereas "https://google.com/pay/" is incorrect. This is important because matching payment
* apps to payment requests happens by string equality. Note that URI.toString() can append
* trailing slashes in some instances.
* @param uri The URI to stringify.
* @return The URI string without a trailing slash, or null if the input parameter is null.
*/
@Nullable
private static String uriToStringWithoutTrailingSlash(@Nullable URI uri) {
if (uri == null) return null;
String string = uri.toString();
return string.endsWith("/") ? string.substring(0, string.length() - 1) : string;
}
} }
...@@ -8,7 +8,6 @@ import android.support.v4.util.ArrayMap; ...@@ -8,7 +8,6 @@ import android.support.v4.util.ArrayMap;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.payments.PaymentApp.InstrumentsCallback; import org.chromium.chrome.browser.payments.PaymentApp.InstrumentsCallback;
import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.WebContents;
import org.chromium.payments.mojom.PaymentMethodData; import org.chromium.payments.mojom.PaymentMethodData;
...@@ -73,10 +72,6 @@ public class PaymentAppFactory implements PaymentAppFactoryInterface { ...@@ -73,10 +72,6 @@ public class PaymentAppFactory implements PaymentAppFactoryInterface {
private PaymentAppFactory() { private PaymentAppFactory() {
mAdditionalFactories = new ArrayList<>(); mAdditionalFactories = new ArrayList<>();
if (ChromeFeatureList.isEnabled(ChromeFeatureList.ANDROID_PAYMENT_APPS)) {
mAdditionalFactories.add(new AndroidPaymentAppFactory());
}
} }
/** /**
......
...@@ -31,6 +31,7 @@ public class PaymentAppService implements PaymentAppFactoryInterface { ...@@ -31,6 +31,7 @@ public class PaymentAppService implements PaymentAppFactoryInterface {
if (ChromeFeatureList.isEnabled(ChromeFeatureList.SERVICE_WORKER_PAYMENT_APPS)) { if (ChromeFeatureList.isEnabled(ChromeFeatureList.SERVICE_WORKER_PAYMENT_APPS)) {
mFactories.add(new ServiceWorkerPaymentAppBridge()); mFactories.add(new ServiceWorkerPaymentAppBridge());
} }
mFactories.add(new AndroidPaymentAppFactory());
} }
/** @param factory The factory to add. */ /** @param factory The factory to add. */
......
...@@ -17,7 +17,6 @@ import org.chromium.base.ApiCompatibilityUtils; ...@@ -17,7 +17,6 @@ import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.autofill.PersonalDataManager; import org.chromium.chrome.browser.autofill.PersonalDataManager;
import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard; import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.payments.AndroidPaymentAppFactory; import org.chromium.chrome.browser.payments.AndroidPaymentAppFactory;
import org.chromium.chrome.browser.payments.ServiceWorkerPaymentAppBridge; import org.chromium.chrome.browser.payments.ServiceWorkerPaymentAppBridge;
import org.chromium.chrome.browser.settings.ChromeSwitchPreference; import org.chromium.chrome.browser.settings.ChromeSwitchPreference;
...@@ -130,16 +129,13 @@ public class AutofillPaymentMethodsFragment extends PreferenceFragmentCompat ...@@ -130,16 +129,13 @@ public class AutofillPaymentMethodsFragment extends PreferenceFragmentCompat
} }
// Add the link to payment apps only after the credit card list is rebuilt. // Add the link to payment apps only after the credit card list is rebuilt.
if (ChromeFeatureList.isEnabled(ChromeFeatureList.ANDROID_PAYMENT_APPS) Preference payment_apps_pref = new Preference(getStyledContext());
|| ChromeFeatureList.isEnabled(ChromeFeatureList.SERVICE_WORKER_PAYMENT_APPS)) { payment_apps_pref.setTitle(R.string.payment_apps_title);
Preference payment_apps_pref = new Preference(getStyledContext()); payment_apps_pref.setFragment(AndroidPaymentAppsFragment.class.getCanonicalName());
payment_apps_pref.setTitle(R.string.payment_apps_title); payment_apps_pref.setShouldDisableView(true);
payment_apps_pref.setFragment(AndroidPaymentAppsFragment.class.getCanonicalName()); payment_apps_pref.setKey(PREF_PAYMENT_APPS);
payment_apps_pref.setShouldDisableView(true); getPreferenceScreen().addPreference(payment_apps_pref);
payment_apps_pref.setKey(PREF_PAYMENT_APPS); refreshPaymentAppsPrefForAndroidPaymentApps(payment_apps_pref);
getPreferenceScreen().addPreference(payment_apps_pref);
refreshPaymentAppsPrefForAndroidPaymentApps(payment_apps_pref);
}
} }
private Context getStyledContext() { private Context getStyledContext() {
......
...@@ -18,27 +18,33 @@ import org.chromium.base.test.util.CommandLineFlags; ...@@ -18,27 +18,33 @@ import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.ChromeActivity; import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeSwitches; import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedCallback;
import org.chromium.chrome.test.ChromeActivityTestRule; import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.payments.PaymentManifestDownloader; import org.chromium.components.payments.PaymentManifestDownloader;
import org.chromium.components.payments.PaymentManifestParser; import org.chromium.components.payments.PaymentManifestParser;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.test.util.Criteria; import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper; import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.net.test.EmbeddedTestServer; import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.payments.mojom.PaymentDetailsModifier;
import org.chromium.payments.mojom.PaymentMethodData;
import org.chromium.url.URI; import org.chromium.url.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** An integration test for the Android payment app finder. */ /** An integration test for the Android payment app finder. */
@RunWith(ChromeJUnit4ClassRunner.class) @RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@MediumTest @MediumTest
public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { public class AndroidPaymentAppFinderTest
implements PaymentAppFactoryDelegate, PaymentAppFactoryParams {
@Rule @Rule
public ChromeActivityTestRule<ChromeActivity> mRule = public ChromeActivityTestRule<ChromeActivity> mRule =
new ChromeActivityTestRule<>(ChromeActivity.class); new ChromeActivityTestRule<>(ChromeActivity.class);
...@@ -83,25 +89,50 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -83,25 +89,50 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
private final TestServerDownloader mDownloader = new TestServerDownloader(); private final TestServerDownloader mDownloader = new TestServerDownloader();
private EmbeddedTestServer mServer; private EmbeddedTestServer mServer;
private List<PaymentApp> mPaymentApps; private List<PaymentInstrument> mPaymentApps;
private boolean mAllPaymentAppsCreated; private boolean mAllPaymentAppsCreated;
private Map<String, PaymentMethodData> mMethodData;
// PaymentAppCreatedCallback // PaymentAppFactoryDelegate implementation.
@Override @Override
public void onPaymentAppCreated(PaymentApp paymentApp) { public PaymentAppFactoryParams getParams() {
return this;
}
// PaymentAppFactoryDelegate implementation.
@Override
public void onPaymentAppCreated(PaymentInstrument paymentApp) {
mPaymentApps.add(paymentApp); mPaymentApps.add(paymentApp);
} }
// PaymentAppCreatedCallback // PaymentAppFactoryDelegate implementation.
@Override @Override
public void onGetPaymentAppsError(String errorMessage) {} public void onPaymentAppCreationError(String errorMessage) {}
// PaymentAppCreatedCallback // PaymentAppFactoryDelegate implementation.
@Override @Override
public void onAllPaymentAppsCreated() { public void onDoneCreatingPaymentApps(PaymentAppFactoryInterface unusedFactory) {
mAllPaymentAppsCreated = true; mAllPaymentAppsCreated = true;
} }
// PaymentAppFactoryParams implementation.
@Override
public WebContents getWebContents() {
return mRule.getActivity().getCurrentWebContents();
}
// PaymentAppFactoryParams implementation.
@Override
public Map<String, PaymentDetailsModifier> getModifiers() {
return Collections.unmodifiableMap(new HashMap<String, PaymentDetailsModifier>());
}
// PaymentAppFactoryParams implementation.
@Override
public Map<String, PaymentMethodData> getMethodData() {
return mMethodData;
}
@Before @Before
public void setUp() throws Throwable { public void setUp() throws Throwable {
mRule.startMainActivityOnBlankPage(); mRule.startMainActivityOnBlankPage();
...@@ -139,7 +170,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -139,7 +170,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
public void testNoMetadata() throws Throwable { public void testNoMetadata() throws Throwable {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("basic-card"); methods.add("basic-card");
mPackageManager.installPaymentApp("BobPay", "com.bobpay", null /* no metadata */, "01"); mPackageManager.installPaymentApp(
"BobPay", "com.bobpay", null /* no metadata */, /*signature=*/"01");
findApps(methods); findApps(methods);
...@@ -163,16 +195,16 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -163,16 +195,16 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("basic-card"); methods.add("basic-card");
mPackageManager.installPaymentApp("AlicePay", "com.alicepay", mPackageManager.installPaymentApp("AlicePay", "com.alicepay",
"" /* no default payment method name in metadata */, "AA"); "" /* no default payment method name in metadata */, /*signature=*/"AA");
mPackageManager.setStringArrayMetaData("com.alicepay", new String[] {"basic-card"}); mPackageManager.setStringArrayMetaData("com.alicepay", new String[] {"basic-card"});
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getIdentifier());
Assert.assertEquals(1, mPaymentApps.get(0).getAppMethodNames().size()); Assert.assertEquals(1, mPaymentApps.get(0).getInstrumentMethodNames().size());
Assert.assertEquals( Assert.assertEquals(
"basic-card", mPaymentApps.get(0).getAppMethodNames().iterator().next()); "basic-card", mPaymentApps.get(0).getInstrumentMethodNames().iterator().next());
mPaymentApps.clear(); mPaymentApps.clear();
mAllPaymentAppsCreated = false; mAllPaymentAppsCreated = false;
...@@ -180,10 +212,10 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -180,10 +212,10 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getIdentifier());
Assert.assertEquals(1, mPaymentApps.get(0).getAppMethodNames().size()); Assert.assertEquals(1, mPaymentApps.get(0).getInstrumentMethodNames().size());
Assert.assertEquals( Assert.assertEquals(
"basic-card", mPaymentApps.get(0).getAppMethodNames().iterator().next()); "basic-card", mPaymentApps.get(0).getInstrumentMethodNames().iterator().next());
} }
/** /**
...@@ -197,17 +229,17 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -197,17 +229,17 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://frankpay.com/webpay"); methods.add("https://frankpay.com/webpay");
mPackageManager.installPaymentApp("AlicePay", "com.alicepay", mPackageManager.installPaymentApp("AlicePay", "com.alicepay",
"" /* no default payment method name in metadata */, "AA"); "" /* no default payment method name in metadata */, /*signature=*/"AA");
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
"com.alicepay", new String[] {"https://frankpay.com/webpay"}); "com.alicepay", new String[] {"https://frankpay.com/webpay"});
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getIdentifier());
Assert.assertEquals(1, mPaymentApps.get(0).getAppMethodNames().size()); Assert.assertEquals(1, mPaymentApps.get(0).getInstrumentMethodNames().size());
Assert.assertEquals("https://frankpay.com/webpay", Assert.assertEquals("https://frankpay.com/webpay",
mPaymentApps.get(0).getAppMethodNames().iterator().next()); mPaymentApps.get(0).getInstrumentMethodNames().iterator().next());
mPaymentApps.clear(); mPaymentApps.clear();
mAllPaymentAppsCreated = false; mAllPaymentAppsCreated = false;
...@@ -215,10 +247,10 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -215,10 +247,10 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getIdentifier());
Assert.assertEquals(1, mPaymentApps.get(0).getAppMethodNames().size()); Assert.assertEquals(1, mPaymentApps.get(0).getInstrumentMethodNames().size());
Assert.assertEquals("https://frankpay.com/webpay", Assert.assertEquals("https://frankpay.com/webpay",
mPaymentApps.get(0).getAppMethodNames().iterator().next()); mPaymentApps.get(0).getInstrumentMethodNames().iterator().next());
} }
/** Payment apps without a human-readable name should be filtered out. */ /** Payment apps without a human-readable name should be filtered out. */
...@@ -227,8 +259,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -227,8 +259,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
public void testEmptyLabel() throws Throwable { public void testEmptyLabel() throws Throwable {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("basic-card"); methods.add("basic-card");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("" /* empty label */, "com.bobpay", "basic-card",
"" /* empty label */, "com.bobpay", "basic-card", "01020304050607080900"); /*signature=*/"01020304050607080900");
findApps(methods); findApps(methods);
...@@ -243,9 +275,9 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -243,9 +275,9 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
methods.add("https://"); // Invalid URI. methods.add("https://"); // Invalid URI.
methods.add("../index.html"); // Relative URI. methods.add("../index.html"); // Relative URI.
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp(
"BobPay", "com.bobpay", "https://", "01020304050607080900"); "BobPay", "com.bobpay", "https://", /*signature=*/"01020304050607080900");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp(
"AlicePay", "com.alicepay", "../index.html", "ABCDEFABCDEFABCDEFAB"); "AlicePay", "com.alicepay", "../index.html", /*signature=*/"ABCDEFABCDEFABCDEFAB");
findApps(methods); findApps(methods);
...@@ -260,10 +292,10 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -260,10 +292,10 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
methods.add("basic-card"); methods.add("basic-card");
methods.add("incorrect-method-name"); // Even if merchant supports it, Chrome filters out methods.add("incorrect-method-name"); // Even if merchant supports it, Chrome filters out
// unknown non-URL method names. // unknown non-URL method names.
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("BobPay", "com.bobpay", "incorrect-method-name",
"BobPay", "com.bobpay", "incorrect-method-name", "01020304050607080900"); /*signature=*/"01020304050607080900");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("AlicePay", "com.alicepay", "incorrect-method-name",
"AlicePay", "com.alicepay", "incorrect-method-name", "ABCDEFABCDEFABCDEFAB"); /*signature=*/"ABCDEFABCDEFABCDEFAB");
findApps(methods); findApps(methods);
...@@ -281,14 +313,14 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -281,14 +313,14 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("basic-card"); methods.add("basic-card");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp(
"BobPay", "com.bobpay", "basic-card", "01020304050607080900"); "BobPay", "com.bobpay", "basic-card", /*signature=*/"01020304050607080900");
mPackageManager.addIsReadyToPayService("com.bobpay"); mPackageManager.addIsReadyToPayService("com.bobpay");
mPackageManager.addIsReadyToPayService("com.alicepay"); mPackageManager.addIsReadyToPayService("com.alicepay");
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getIdentifier());
} }
/** /**
...@@ -302,13 +334,13 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -302,13 +334,13 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
public void testOneUrlMethodNameApp() throws Throwable { public void testOneUrlMethodNameApp() throws Throwable {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://bobpay.com/webpay"); methods.add("https://bobpay.com/webpay");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("BobPay", "com.bobpay", "https://bobpay.com/webpay",
"BobPay", "com.bobpay", "https://bobpay.com/webpay", "01020304050607080900"); /*signature=*/"01020304050607080900");
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getIdentifier());
} }
/** /**
...@@ -365,16 +397,16 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -365,16 +397,16 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("basic-card"); methods.add("basic-card");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp(
"BobPay", "com.bobpay", "basic-card", "01020304050607080900"); "BobPay", "com.bobpay", "basic-card", /*signature=*/"01020304050607080900");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp(
"AlicePay", "com.alicepay", "basic-card", "ABCDEFABCDEFABCDEFAB"); "AlicePay", "com.alicepay", "basic-card", /*signature=*/"ABCDEFABCDEFABCDEFAB");
findApps(methods); findApps(methods);
Assert.assertEquals("2 apps should match the query", 2, mPaymentApps.size()); Assert.assertEquals("2 apps should match the query", 2, mPaymentApps.size());
Set<String> appIdentifiers = new HashSet<>(); Set<String> appIdentifiers = new HashSet<>();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.bobpay")); Assert.assertTrue(appIdentifiers.contains("com.bobpay"));
Assert.assertTrue(appIdentifiers.contains("com.alicepay")); Assert.assertTrue(appIdentifiers.contains("com.alicepay"));
} }
...@@ -391,16 +423,16 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -391,16 +423,16 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://davepay.com/webpay"); methods.add("https://davepay.com/webpay");
mPackageManager.installPaymentApp("DavePay", "com.davepay.prod", mPackageManager.installPaymentApp("DavePay", "com.davepay.prod",
"https://davepay.com/webpay", "44444444442222222222"); "https://davepay.com/webpay", /*signature=*/"44444444442222222222");
mPackageManager.installPaymentApp("DavePay Dev", "com.davepay.dev", mPackageManager.installPaymentApp("DavePay Dev", "com.davepay.dev",
"https://davepay.com/webpay", "44444444441111111111"); "https://davepay.com/webpay", /*signature=*/"44444444441111111111");
findApps(methods); findApps(methods);
Assert.assertEquals("2 apps should match the query", 2, mPaymentApps.size()); Assert.assertEquals("2 apps should match the query", 2, mPaymentApps.size());
Set<String> appIdentifiers = new HashSet<>(); Set<String> appIdentifiers = new HashSet<>();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.davepay.prod")); Assert.assertTrue(appIdentifiers.contains("com.davepay.prod"));
Assert.assertTrue(appIdentifiers.contains("com.davepay.dev")); Assert.assertTrue(appIdentifiers.contains("com.davepay.dev"));
...@@ -411,8 +443,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -411,8 +443,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Assert.assertEquals("2 apps should match the query again", 2, mPaymentApps.size()); Assert.assertEquals("2 apps should match the query again", 2, mPaymentApps.size());
appIdentifiers.clear(); appIdentifiers.clear();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.davepay.prod")); Assert.assertTrue(appIdentifiers.contains("com.davepay.prod"));
Assert.assertTrue(appIdentifiers.contains("com.davepay.dev")); Assert.assertTrue(appIdentifiers.contains("com.davepay.dev"));
} }
...@@ -428,17 +460,17 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -428,17 +460,17 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://bobpay.com/webpay"); methods.add("https://bobpay.com/webpay");
methods.add("https://alicepay.com/webpay"); methods.add("https://alicepay.com/webpay");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("BobPay", "com.bobpay", "https://bobpay.com/webpay",
"BobPay", "com.bobpay", "https://bobpay.com/webpay", "01020304050607080900"); /*signature=*/"01020304050607080900");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("AlicePay", "com.alicepay", "https://alicepay.com/webpay",
"AlicePay", "com.alicepay", "https://alicepay.com/webpay", "ABCDEFABCDEFABCDEFAB"); /*signature=*/"ABCDEFABCDEFABCDEFAB");
findApps(methods); findApps(methods);
Assert.assertEquals("2 apps should match the query", 2, mPaymentApps.size()); Assert.assertEquals("2 apps should match the query", 2, mPaymentApps.size());
Set<String> appIdentifiers = new HashSet<>(); Set<String> appIdentifiers = new HashSet<>();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.bobpay")); Assert.assertTrue(appIdentifiers.contains("com.bobpay"));
Assert.assertTrue(appIdentifiers.contains("com.alicepay")); Assert.assertTrue(appIdentifiers.contains("com.alicepay"));
...@@ -449,8 +481,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -449,8 +481,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Assert.assertEquals("2 apps should match the query again", 2, mPaymentApps.size()); Assert.assertEquals("2 apps should match the query again", 2, mPaymentApps.size());
appIdentifiers.clear(); appIdentifiers.clear();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.bobpay")); Assert.assertTrue(appIdentifiers.contains("com.bobpay"));
Assert.assertTrue(appIdentifiers.contains("com.alicepay")); Assert.assertTrue(appIdentifiers.contains("com.alicepay"));
} }
...@@ -466,15 +498,15 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -466,15 +498,15 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://bobpay.com/webpay"); methods.add("https://bobpay.com/webpay");
methods.add("https://not-valid.com/webpay"); methods.add("https://not-valid.com/webpay");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("BobPay", "com.bobpay", "https://bobpay.com/webpay",
"BobPay", "com.bobpay", "https://bobpay.com/webpay", "01020304050607080900"); /*signature=*/"01020304050607080900");
mPackageManager.installPaymentApp("NotValid", "com.not-valid", mPackageManager.installPaymentApp("NotValid", "com.not-valid",
"https://not-valid.com/webpay", "ABCDEFABCDEFABCDEFAB"); "https://not-valid.com/webpay", /*signature=*/"ABCDEFABCDEFABCDEFAB");
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getIdentifier());
mPaymentApps.clear(); mPaymentApps.clear();
mAllPaymentAppsCreated = false; mAllPaymentAppsCreated = false;
...@@ -482,7 +514,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -482,7 +514,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query again", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query again", 1, mPaymentApps.size());
Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getIdentifier());
} }
/** /**
...@@ -495,17 +527,17 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -495,17 +527,17 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://bobpay.com/webpay"); methods.add("https://bobpay.com/webpay");
methods.add("https://alicepay.com/webpay"); methods.add("https://alicepay.com/webpay");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("BobPay", "com.bobpay", "https://bobpay.com/webpay",
"BobPay", "com.bobpay", "https://bobpay.com/webpay", "01020304050607080900"); /*signature=*/"01020304050607080900");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("AlicePay", "com.alicepay", "https://alicepay.com/webpay",
"AlicePay", "com.alicepay", "https://alicepay.com/webpay", "ABCDEFABCDEFABCDEFAB"); /*signature=*/"ABCDEFABCDEFABCDEFAB");
findApps(methods); findApps(methods);
Assert.assertEquals("2 apps should match the query", 2, mPaymentApps.size()); Assert.assertEquals("2 apps should match the query", 2, mPaymentApps.size());
Set<String> appIdentifiers = new HashSet<>(); Set<String> appIdentifiers = new HashSet<>();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.bobpay")); Assert.assertTrue(appIdentifiers.contains("com.bobpay"));
Assert.assertTrue(appIdentifiers.contains("com.alicepay")); Assert.assertTrue(appIdentifiers.contains("com.alicepay"));
...@@ -516,8 +548,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -516,8 +548,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Assert.assertEquals("2 apps should still match the query", 2, mPaymentApps.size()); Assert.assertEquals("2 apps should still match the query", 2, mPaymentApps.size());
appIdentifiers.clear(); appIdentifiers.clear();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.bobpay")); Assert.assertTrue(appIdentifiers.contains("com.bobpay"));
Assert.assertTrue(appIdentifiers.contains("com.alicepay")); Assert.assertTrue(appIdentifiers.contains("com.alicepay"));
} }
...@@ -533,16 +565,16 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -533,16 +565,16 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://charliepay.com/webpay"); methods.add("https://charliepay.com/webpay");
mPackageManager.installPaymentApp("CharliePay", "com.charliepay.dev", mPackageManager.installPaymentApp("CharliePay", "com.charliepay.dev",
"https://charliepay.com/webpay", "33333333333111111111"); "https://charliepay.com/webpay", /*signature=*/"33333333333111111111");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("BobPay", "com.bobpay", "https://bobpay.com/webpay",
"BobPay", "com.bobpay", "https://bobpay.com/webpay", "01020304050607080900"); /*signature=*/"01020304050607080900");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("AlicePay", "com.alicepay", "https://alicepay.com/webpay",
"AlicePay", "com.alicepay", "https://alicepay.com/webpay", "ABCDEFABCDEFABCDEFAB"); /*signature=*/"ABCDEFABCDEFABCDEFAB");
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.charliepay.dev", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.charliepay.dev", mPaymentApps.get(0).getIdentifier());
mPaymentApps.clear(); mPaymentApps.clear();
mAllPaymentAppsCreated = false; mAllPaymentAppsCreated = false;
...@@ -550,7 +582,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -550,7 +582,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query again", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query again", 1, mPaymentApps.size());
Assert.assertEquals("com.charliepay.dev", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.charliepay.dev", mPaymentApps.get(0).getIdentifier());
} }
/** /**
...@@ -563,17 +595,17 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -563,17 +595,17 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
public void testDavePayDev() throws Throwable { public void testDavePayDev() throws Throwable {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://davepay.com/webpay"); methods.add("https://davepay.com/webpay");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("DavePay", "com.davepay.dev",
"DavePay", "com.davepay.dev", "https://davepay.com/webpay", "44444444441111111111"); "https://davepay.com/webpay", /*signature=*/"44444444441111111111");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("BobPay", "com.bobpay", "https://bobpay.com/webpay",
"BobPay", "com.bobpay", "https://bobpay.com/webpay", "01020304050607080900"); /*signature=*/"01020304050607080900");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("AlicePay", "com.alicepay", "https://alicepay.com/webpay",
"AlicePay", "com.alicepay", "https://alicepay.com/webpay", "ABCDEFABCDEFABCDEFAB"); /*signature=*/"ABCDEFABCDEFABCDEFAB");
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.davepay.dev", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.davepay.dev", mPaymentApps.get(0).getIdentifier());
mPaymentApps.clear(); mPaymentApps.clear();
mAllPaymentAppsCreated = false; mAllPaymentAppsCreated = false;
...@@ -581,7 +613,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -581,7 +613,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query again", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query again", 1, mPaymentApps.size());
Assert.assertEquals("com.davepay.dev", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.davepay.dev", mPaymentApps.get(0).getIdentifier());
} }
/** /**
...@@ -595,13 +627,13 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -595,13 +627,13 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
public void testValidEvePay1() throws Throwable { public void testValidEvePay1() throws Throwable {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://evepay.com/webpay"); methods.add("https://evepay.com/webpay");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("EvePay", "com.evepay", "https://evepay.com/webpay",
"EvePay", "com.evepay", "https://evepay.com/webpay", "55555555551111111111"); /*signature=*/"55555555551111111111");
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.evepay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.evepay", mPaymentApps.get(0).getIdentifier());
mPaymentApps.clear(); mPaymentApps.clear();
mAllPaymentAppsCreated = false; mAllPaymentAppsCreated = false;
...@@ -609,7 +641,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -609,7 +641,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query again", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query again", 1, mPaymentApps.size());
Assert.assertEquals("com.evepay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.evepay", mPaymentApps.get(0).getIdentifier());
} }
/** /**
...@@ -623,13 +655,13 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -623,13 +655,13 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
public void testValidEvePay2() throws Throwable { public void testValidEvePay2() throws Throwable {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://evepay.com/webpay"); methods.add("https://evepay.com/webpay");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("EvePay", "com.evepay", "https://evepay.com/webpay",
"EvePay", "com.evepay", "https://evepay.com/webpay", "55555555552222222222"); /*signature=*/"55555555552222222222");
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.evepay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.evepay", mPaymentApps.get(0).getIdentifier());
mPaymentApps.clear(); mPaymentApps.clear();
mAllPaymentAppsCreated = false; mAllPaymentAppsCreated = false;
...@@ -637,7 +669,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -637,7 +669,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query again", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query again", 1, mPaymentApps.size());
Assert.assertEquals("com.evepay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.evepay", mPaymentApps.get(0).getIdentifier());
} }
/** /**
...@@ -651,7 +683,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -651,7 +683,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://evepay.com/webpay"); methods.add("https://evepay.com/webpay");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp(
"EvePay", "com.evepay", "https://evepay.com/webpay", "55"); "EvePay", "com.evepay", "https://evepay.com/webpay", /*signature=*/"55");
findApps(methods); findApps(methods);
...@@ -675,10 +707,10 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -675,10 +707,10 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://frankpay.com/webpay"); methods.add("https://frankpay.com/webpay");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp(
"AlicePay", "com.alicepay", "https://alicepay.com/webpay", "00"); "AlicePay", "com.alicepay", "https://alicepay.com/webpay", /*signature=*/"00");
mPackageManager.installPaymentApp("BobPay", "com.bobpay", "basic-card", "11"); mPackageManager.installPaymentApp("BobPay", "com.bobpay", "basic-card", /*signature=*/"11");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp(
"AlicePay", "com.charliepay", "invalid-payment-method-name", "22"); "AlicePay", "com.charliepay", "invalid-payment-method-name", /*signature=*/"22");
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
"com.alicepay", new String[] {"https://frankpay.com/webpay"}); "com.alicepay", new String[] {"https://frankpay.com/webpay"});
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
...@@ -690,9 +722,9 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -690,9 +722,9 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Assert.assertEquals("3 apps should match the query", 3, mPaymentApps.size()); Assert.assertEquals("3 apps should match the query", 3, mPaymentApps.size());
Set<String> appIdentifiers = new HashSet<>(); Set<String> appIdentifiers = new HashSet<>();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
appIdentifiers.add(mPaymentApps.get(2).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(2).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.alicepay")); Assert.assertTrue(appIdentifiers.contains("com.alicepay"));
Assert.assertTrue(appIdentifiers.contains("com.bobpay")); Assert.assertTrue(appIdentifiers.contains("com.bobpay"));
Assert.assertTrue(appIdentifiers.contains("com.charliepay")); Assert.assertTrue(appIdentifiers.contains("com.charliepay"));
...@@ -704,9 +736,9 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -704,9 +736,9 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Assert.assertEquals("3 apps should still match the query", 3, mPaymentApps.size()); Assert.assertEquals("3 apps should still match the query", 3, mPaymentApps.size());
appIdentifiers.clear(); appIdentifiers.clear();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
appIdentifiers.add(mPaymentApps.get(2).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(2).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.alicepay")); Assert.assertTrue(appIdentifiers.contains("com.alicepay"));
Assert.assertTrue(appIdentifiers.contains("com.bobpay")); Assert.assertTrue(appIdentifiers.contains("com.bobpay"));
Assert.assertTrue(appIdentifiers.contains("com.charliepay")); Assert.assertTrue(appIdentifiers.contains("com.charliepay"));
...@@ -732,11 +764,11 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -732,11 +764,11 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getIdentifier());
Assert.assertEquals("1 payment method should be enabled", 1, Assert.assertEquals("1 payment method should be enabled", 1,
mPaymentApps.get(0).getAppMethodNames().size()); mPaymentApps.get(0).getInstrumentMethodNames().size());
Assert.assertEquals("https://frankpay.com/webpay", Assert.assertEquals("https://frankpay.com/webpay",
mPaymentApps.get(0).getAppMethodNames().iterator().next()); mPaymentApps.get(0).getInstrumentMethodNames().iterator().next());
mPaymentApps.clear(); mPaymentApps.clear();
mAllPaymentAppsCreated = false; mAllPaymentAppsCreated = false;
...@@ -744,11 +776,11 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -744,11 +776,11 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getIdentifier());
Assert.assertEquals("1 payment method should still be enabled", 1, Assert.assertEquals("1 payment method should still be enabled", 1,
mPaymentApps.get(0).getAppMethodNames().size()); mPaymentApps.get(0).getInstrumentMethodNames().size());
Assert.assertEquals("https://frankpay.com/webpay", Assert.assertEquals("https://frankpay.com/webpay",
mPaymentApps.get(0).getAppMethodNames().iterator().next()); mPaymentApps.get(0).getInstrumentMethodNames().iterator().next());
} }
/** /**
...@@ -762,8 +794,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -762,8 +794,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://georgepay.com/webpay"); methods.add("https://georgepay.com/webpay");
// Valid AlicePay: // Valid AlicePay:
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("AlicePay", "com.alicepay", "https://alicepay.com/webpay",
"AlicePay", "com.alicepay", "https://alicepay.com/webpay", "ABCDEFABCDEFABCDEFAB"); /*signature=*/"ABCDEFABCDEFABCDEFAB");
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
"com.alicepay", new String[] {"https://georgepay.com/webpay"}); "com.alicepay", new String[] {"https://georgepay.com/webpay"});
// Invalid AlicePay: // Invalid AlicePay:
...@@ -772,20 +804,20 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -772,20 +804,20 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
"com.fake-alicepay", new String[] {"https://georgepay.com/webpay"}); "com.fake-alicepay", new String[] {"https://georgepay.com/webpay"});
// Valid BobPay: // Valid BobPay:
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("BobPay", "com.bobpay", "https://bobpay.com/webpay",
"BobPay", "com.bobpay", "https://bobpay.com/webpay", "01020304050607080900"); /*signature=*/"01020304050607080900");
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
"com.bobpay", new String[] {"https://georgepay.com/webpay"}); "com.bobpay", new String[] {"https://georgepay.com/webpay"});
// A "basic-card" app. // A "basic-card" app.
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("CharliePay", "com.charliepay.dev", "basic-card",
"CharliePay", "com.charliepay.dev", "basic-card", "33333333333111111111"); /*signature=*/"33333333333111111111");
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
"com.charliepay.dev", new String[] {"https://georgepay.com/webpay"}); "com.charliepay.dev", new String[] {"https://georgepay.com/webpay"});
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getIdentifier());
mPaymentApps.clear(); mPaymentApps.clear();
mAllPaymentAppsCreated = false; mAllPaymentAppsCreated = false;
...@@ -793,7 +825,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -793,7 +825,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getIdentifier());
} }
/** /**
...@@ -835,19 +867,19 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -835,19 +867,19 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://bobpay.com/webpay"); methods.add("https://bobpay.com/webpay");
methods.add("https://georgepay.com/webpay"); methods.add("https://georgepay.com/webpay");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("BobPay", "com.bobpay", "https://bobpay.com/webpay",
"BobPay", "com.bobpay", "https://bobpay.com/webpay", "01020304050607080900"); /*signature=*/"01020304050607080900");
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
"com.bobpay", new String[] {"https://georgepay.com/webpay"}); "com.bobpay", new String[] {"https://georgepay.com/webpay"});
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getIdentifier());
Assert.assertEquals("1 payment method should be enabled", 1, Assert.assertEquals("1 payment method should be enabled", 1,
mPaymentApps.get(0).getAppMethodNames().size()); mPaymentApps.get(0).getInstrumentMethodNames().size());
Assert.assertEquals("https://bobpay.com/webpay", Assert.assertEquals("https://bobpay.com/webpay",
mPaymentApps.get(0).getAppMethodNames().iterator().next()); mPaymentApps.get(0).getInstrumentMethodNames().iterator().next());
mPaymentApps.clear(); mPaymentApps.clear();
mAllPaymentAppsCreated = false; mAllPaymentAppsCreated = false;
...@@ -855,11 +887,11 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -855,11 +887,11 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getIdentifier());
Assert.assertEquals("1 payment method should still be enabled", 1, Assert.assertEquals("1 payment method should still be enabled", 1,
mPaymentApps.get(0).getAppMethodNames().size()); mPaymentApps.get(0).getInstrumentMethodNames().size());
Assert.assertEquals("https://bobpay.com/webpay", Assert.assertEquals("https://bobpay.com/webpay",
mPaymentApps.get(0).getAppMethodNames().iterator().next()); mPaymentApps.get(0).getInstrumentMethodNames().iterator().next());
} }
/** /**
...@@ -874,10 +906,10 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -874,10 +906,10 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
throws Throwable { throws Throwable {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://henrypay.com/webpay"); methods.add("https://henrypay.com/webpay");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("HenryPay", "com.henrypay", "https://henrypay.com/webpay",
"HenryPay", "com.henrypay", "https://henrypay.com/webpay", "55555555551111111111"); /*signature=*/"55555555551111111111");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("BobPay", "com.bobpay", "https://bobpay.com/webpay",
"BobPay", "com.bobpay", "https://bobpay.com/webpay", "01020304050607080900"); /*signature=*/"01020304050607080900");
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
"com.bobpay", new String[] {"https://henrypay.com/webpay"}); "com.bobpay", new String[] {"https://henrypay.com/webpay"});
...@@ -885,8 +917,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -885,8 +917,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Assert.assertEquals("2 apps should match the query", 2, mPaymentApps.size()); Assert.assertEquals("2 apps should match the query", 2, mPaymentApps.size());
Set<String> appIdentifiers = new HashSet<>(); Set<String> appIdentifiers = new HashSet<>();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.henrypay")); Assert.assertTrue(appIdentifiers.contains("com.henrypay"));
Assert.assertTrue(appIdentifiers.contains("com.bobpay")); Assert.assertTrue(appIdentifiers.contains("com.bobpay"));
...@@ -897,8 +929,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -897,8 +929,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Assert.assertEquals("2 apps should still match the query", 2, mPaymentApps.size()); Assert.assertEquals("2 apps should still match the query", 2, mPaymentApps.size());
appIdentifiers.clear(); appIdentifiers.clear();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.henrypay")); Assert.assertTrue(appIdentifiers.contains("com.henrypay"));
Assert.assertTrue(appIdentifiers.contains("com.bobpay")); Assert.assertTrue(appIdentifiers.contains("com.bobpay"));
} }
...@@ -915,18 +947,18 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -915,18 +947,18 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
throws Throwable { throws Throwable {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://henrypay.com/webpay"); methods.add("https://henrypay.com/webpay");
mPackageManager.installPaymentApp("BobPay", "com.bobpay", "basic-card", "AA"); mPackageManager.installPaymentApp("BobPay", "com.bobpay", "basic-card", /*signature=*/"AA");
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
"com.bobpay", new String[] {"https://henrypay.com/webpay"}); "com.bobpay", new String[] {"https://henrypay.com/webpay"});
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getIdentifier());
Assert.assertEquals("1 payment method should be enabled", 1, Assert.assertEquals("1 payment method should be enabled", 1,
mPaymentApps.get(0).getAppMethodNames().size()); mPaymentApps.get(0).getInstrumentMethodNames().size());
Assert.assertEquals("https://henrypay.com/webpay", Assert.assertEquals("https://henrypay.com/webpay",
mPaymentApps.get(0).getAppMethodNames().iterator().next()); mPaymentApps.get(0).getInstrumentMethodNames().iterator().next());
mPaymentApps.clear(); mPaymentApps.clear();
mAllPaymentAppsCreated = false; mAllPaymentAppsCreated = false;
...@@ -934,11 +966,11 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -934,11 +966,11 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.bobpay", mPaymentApps.get(0).getIdentifier());
Assert.assertEquals("1 payment method should still be enabled", 1, Assert.assertEquals("1 payment method should still be enabled", 1,
mPaymentApps.get(0).getAppMethodNames().size()); mPaymentApps.get(0).getInstrumentMethodNames().size());
Assert.assertEquals("https://henrypay.com/webpay", Assert.assertEquals("https://henrypay.com/webpay",
mPaymentApps.get(0).getAppMethodNames().iterator().next()); mPaymentApps.get(0).getInstrumentMethodNames().iterator().next());
} }
/** /**
...@@ -953,14 +985,14 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -953,14 +985,14 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
public void testUrlPaymentMethodWithDefaultApplicationAndOneSupportedOrigin() throws Throwable { public void testUrlPaymentMethodWithDefaultApplicationAndOneSupportedOrigin() throws Throwable {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://ikepay.com/webpay"); methods.add("https://ikepay.com/webpay");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("IkePay", "com.ikepay", "https://ikepay.com/webpay",
"IkePay", "com.ikepay", "https://ikepay.com/webpay", "66666666661111111111"); /*signature=*/"66666666661111111111");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("AlicePay", "com.alicepay", "https://alicepay.com/webpay",
"AlicePay", "com.alicepay", "https://alicepay.com/webpay", "ABCDEFABCDEFABCDEFAB"); /*signature=*/"ABCDEFABCDEFABCDEFAB");
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
"com.alicepay", new String[] {"https://ikepay.com/webpay"}); "com.alicepay", new String[] {"https://ikepay.com/webpay"});
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("BobPay", "com.bobpay", "https://bobpay.com/webpay",
"BobPay", "com.bobpay", "https://bobpay.com/webpay", "01020304050607080900"); /*signature=*/"01020304050607080900");
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
"com.bobpay", new String[] {"https://ikepay.com/webpay"}); "com.bobpay", new String[] {"https://ikepay.com/webpay"});
...@@ -968,8 +1000,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -968,8 +1000,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Assert.assertEquals("2 apps should match the query", 2, mPaymentApps.size()); Assert.assertEquals("2 apps should match the query", 2, mPaymentApps.size());
Set<String> appIdentifiers = new HashSet<>(); Set<String> appIdentifiers = new HashSet<>();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.ikepay")); Assert.assertTrue(appIdentifiers.contains("com.ikepay"));
Assert.assertTrue(appIdentifiers.contains("com.alicepay")); Assert.assertTrue(appIdentifiers.contains("com.alicepay"));
...@@ -980,8 +1012,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -980,8 +1012,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Assert.assertEquals("2 apps should still match the query", 2, mPaymentApps.size()); Assert.assertEquals("2 apps should still match the query", 2, mPaymentApps.size());
appIdentifiers.clear(); appIdentifiers.clear();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.ikepay")); Assert.assertTrue(appIdentifiers.contains("com.ikepay"));
Assert.assertTrue(appIdentifiers.contains("com.alicepay")); Assert.assertTrue(appIdentifiers.contains("com.alicepay"));
} }
...@@ -996,15 +1028,15 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -996,15 +1028,15 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
public void testDuplicateDefaultAndSupportedMethodAndAllOriginsSupported() throws Throwable { public void testDuplicateDefaultAndSupportedMethodAndAllOriginsSupported() throws Throwable {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://henrypay.com/webpay"); methods.add("https://henrypay.com/webpay");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("HenryPay", "com.henrypay", "https://henrypay.com/webpay",
"HenryPay", "com.henrypay", "https://henrypay.com/webpay", "55555555551111111111"); /*signature=*/"55555555551111111111");
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
"com.henrypay", new String[] {"https://henrypay.com/webpay"}); "com.henrypay", new String[] {"https://henrypay.com/webpay"});
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.henrypay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.henrypay", mPaymentApps.get(0).getIdentifier());
mPaymentApps.clear(); mPaymentApps.clear();
mAllPaymentAppsCreated = false; mAllPaymentAppsCreated = false;
...@@ -1012,7 +1044,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -1012,7 +1044,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.henrypay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.henrypay", mPaymentApps.get(0).getIdentifier());
} }
/** /**
...@@ -1024,10 +1056,10 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -1024,10 +1056,10 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
public void testTwoAppsFromDifferentOriginsWithTheSamePaymentMethod() throws Throwable { public void testTwoAppsFromDifferentOriginsWithTheSamePaymentMethod() throws Throwable {
Set<String> methods = new HashSet<>(); Set<String> methods = new HashSet<>();
methods.add("https://jonpay.com/webpay"); methods.add("https://jonpay.com/webpay");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("AlicePay", "com.alicepay", "https://alicepay.com/webpay",
"AlicePay", "com.alicepay", "https://alicepay.com/webpay", "ABCDEFABCDEFABCDEFAB"); /*signature=*/"ABCDEFABCDEFABCDEFAB");
mPackageManager.installPaymentApp( mPackageManager.installPaymentApp("BobPay", "com.bobpay", "https://bobpay.com/webpay",
"BobPay", "com.bobpay", "https://bobpay.com/webpay", "01020304050607080900"); /*signature=*/"01020304050607080900");
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
"com.alicepay", new String[] {"https://jonpay.com/webpay"}); "com.alicepay", new String[] {"https://jonpay.com/webpay"});
mPackageManager.setStringArrayMetaData( mPackageManager.setStringArrayMetaData(
...@@ -1037,8 +1069,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -1037,8 +1069,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Assert.assertEquals("2 apps should match the query", 2, mPaymentApps.size()); Assert.assertEquals("2 apps should match the query", 2, mPaymentApps.size());
Set<String> appIdentifiers = new HashSet<>(); Set<String> appIdentifiers = new HashSet<>();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.alicepay")); Assert.assertTrue(appIdentifiers.contains("com.alicepay"));
Assert.assertTrue(appIdentifiers.contains("com.bobpay")); Assert.assertTrue(appIdentifiers.contains("com.bobpay"));
...@@ -1049,8 +1081,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -1049,8 +1081,8 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
Assert.assertEquals("2 apps should still match the query", 2, mPaymentApps.size()); Assert.assertEquals("2 apps should still match the query", 2, mPaymentApps.size());
appIdentifiers.clear(); appIdentifiers.clear();
appIdentifiers.add(mPaymentApps.get(0).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(0).getIdentifier());
appIdentifiers.add(mPaymentApps.get(1).getAppIdentifier()); appIdentifiers.add(mPaymentApps.get(1).getIdentifier());
Assert.assertTrue(appIdentifiers.contains("com.alicepay")); Assert.assertTrue(appIdentifiers.contains("com.alicepay"));
Assert.assertTrue(appIdentifiers.contains("com.bobpay")); Assert.assertTrue(appIdentifiers.contains("com.bobpay"));
} }
...@@ -1069,7 +1101,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -1069,7 +1101,7 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
methods.add("tokenized-card"); methods.add("tokenized-card");
methods.add("not-supported"); methods.add("not-supported");
mPackageManager.installPaymentApp("AlicePay", "com.alicepay", mPackageManager.installPaymentApp("AlicePay", "com.alicepay",
"" /* no default payment method name in metadata */, "AA"); "" /* no default payment method name in metadata */, /*signature=*/"AA");
mPackageManager.setStringArrayMetaData("com.alicepay", mPackageManager.setStringArrayMetaData("com.alicepay",
new String[] {"basic-card", "interledger", "payee-credit-transfer", new String[] {"basic-card", "interledger", "payee-credit-transfer",
"payer-credit-transfer", "tokenized-card", "not-supported"}); "payer-credit-transfer", "tokenized-card", "not-supported"});
...@@ -1077,16 +1109,16 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -1077,16 +1109,16 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getIdentifier());
Assert.assertEquals(5, mPaymentApps.get(0).getAppMethodNames().size()); Assert.assertEquals(5, mPaymentApps.get(0).getInstrumentMethodNames().size());
Assert.assertTrue(mPaymentApps.get(0).getAppMethodNames().contains("basic-card")); Assert.assertTrue(mPaymentApps.get(0).getInstrumentMethodNames().contains("basic-card"));
Assert.assertTrue(mPaymentApps.get(0).getAppMethodNames().contains("interledger")); Assert.assertTrue(mPaymentApps.get(0).getInstrumentMethodNames().contains("interledger"));
Assert.assertTrue( Assert.assertTrue(
mPaymentApps.get(0).getAppMethodNames().contains("payee-credit-transfer")); mPaymentApps.get(0).getInstrumentMethodNames().contains("payee-credit-transfer"));
Assert.assertTrue( Assert.assertTrue(
mPaymentApps.get(0).getAppMethodNames().contains("payer-credit-transfer")); mPaymentApps.get(0).getInstrumentMethodNames().contains("payer-credit-transfer"));
Assert.assertTrue( Assert.assertTrue(
mPaymentApps.get(0).getAppMethodNames().contains("tokenized-card")); mPaymentApps.get(0).getInstrumentMethodNames().contains("tokenized-card"));
mPaymentApps.clear(); mPaymentApps.clear();
mAllPaymentAppsCreated = false; mAllPaymentAppsCreated = false;
...@@ -1094,25 +1126,28 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -1094,25 +1126,28 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
findApps(methods); findApps(methods);
Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size()); Assert.assertEquals("1 app should still match the query", 1, mPaymentApps.size());
Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getAppIdentifier()); Assert.assertEquals("com.alicepay", mPaymentApps.get(0).getIdentifier());
Assert.assertEquals(5, mPaymentApps.get(0).getAppMethodNames().size()); Assert.assertEquals(5, mPaymentApps.get(0).getInstrumentMethodNames().size());
Assert.assertTrue(mPaymentApps.get(0).getAppMethodNames().contains("basic-card")); Assert.assertTrue(mPaymentApps.get(0).getInstrumentMethodNames().contains("basic-card"));
Assert.assertTrue(mPaymentApps.get(0).getAppMethodNames().contains("interledger")); Assert.assertTrue(mPaymentApps.get(0).getInstrumentMethodNames().contains("interledger"));
Assert.assertTrue( Assert.assertTrue(
mPaymentApps.get(0).getAppMethodNames().contains("payee-credit-transfer")); mPaymentApps.get(0).getInstrumentMethodNames().contains("payee-credit-transfer"));
Assert.assertTrue( Assert.assertTrue(
mPaymentApps.get(0).getAppMethodNames().contains("payer-credit-transfer")); mPaymentApps.get(0).getInstrumentMethodNames().contains("payer-credit-transfer"));
Assert.assertTrue( Assert.assertTrue(
mPaymentApps.get(0).getAppMethodNames().contains("tokenized-card")); mPaymentApps.get(0).getInstrumentMethodNames().contains("tokenized-card"));
} }
private void findApps(final Set<String> methodNames) throws Throwable { private void findApps(final Set<String> methodNames) throws Throwable {
mRule.runOnUiThread( mMethodData = buildMethodData(methodNames);
() mRule.runOnUiThread(() -> {
-> AndroidPaymentAppFinder.find(mRule.getActivity().getCurrentWebContents(), AndroidPaymentAppFinder finder =
methodNames, new PaymentManifestWebDataService(), mDownloader, new AndroidPaymentAppFinder(new PaymentManifestWebDataService(), mDownloader,
new PaymentManifestParser(), mPackageManager, new PaymentManifestParser(), mPackageManager,
AndroidPaymentAppFinderTest.this)); /*delegate=*/AndroidPaymentAppFinderTest.this, /*factory=*/null);
finder.bypassIsReadyToPayServiceInTest();
finder.findAndroidPaymentApps();
});
CriteriaHelper.pollInstrumentationThread(new Criteria() { CriteriaHelper.pollInstrumentationThread(new Criteria() {
@Override @Override
public boolean isSatisfied() { public boolean isSatisfied() {
...@@ -1120,4 +1155,14 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback { ...@@ -1120,4 +1155,14 @@ public class AndroidPaymentAppFinderTest implements PaymentAppCreatedCallback {
} }
}); });
} }
private static Map<String, PaymentMethodData> buildMethodData(Set<String> methodNames) {
Map<String, PaymentMethodData> result = new HashMap<>();
for (String methodName : methodNames) {
PaymentMethodData methodData = new PaymentMethodData();
methodData.supportedMethod = methodName;
result.put(methodName, methodData);
}
return result;
}
} }
...@@ -39,7 +39,6 @@ import java.util.concurrent.TimeoutException; ...@@ -39,7 +39,6 @@ import java.util.concurrent.TimeoutException;
@CommandLineFlags.Add({ @CommandLineFlags.Add({
ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
// Speed up the test by not looking up actual apps installed on the device. // Speed up the test by not looking up actual apps installed on the device.
"disable-features=" + ChromeFeatureList.ANDROID_PAYMENT_APPS,
"disable-features=" + ChromeFeatureList.SERVICE_WORKER_PAYMENT_APPS, "disable-features=" + ChromeFeatureList.SERVICE_WORKER_PAYMENT_APPS,
}) })
public class PaymentRequestPaymentAppUiSkipPreloadTest { public class PaymentRequestPaymentAppUiSkipPreloadTest {
......
...@@ -36,7 +36,6 @@ import java.util.concurrent.TimeoutException; ...@@ -36,7 +36,6 @@ import java.util.concurrent.TimeoutException;
@RunWith(ChromeJUnit4ClassRunner.class) @RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
// Speed up the test by not looking up actual apps installed on the device. // Speed up the test by not looking up actual apps installed on the device.
"disable-features=" + ChromeFeatureList.ANDROID_PAYMENT_APPS,
"disable-features=" + ChromeFeatureList.SERVICE_WORKER_PAYMENT_APPS}) "disable-features=" + ChromeFeatureList.SERVICE_WORKER_PAYMENT_APPS})
public class PaymentRequestPaymentAppUiSkipTest { public class PaymentRequestPaymentAppUiSkipTest {
// Disable animations to reduce flakiness. // Disable animations to reduce flakiness.
......
...@@ -20,18 +20,19 @@ import org.mockito.Mockito; ...@@ -20,18 +20,19 @@ import org.mockito.Mockito;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.CalledByNativeJavaTest; import org.chromium.base.annotations.CalledByNativeJavaTest;
import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedCallback;
import org.chromium.components.payments.PaymentManifestDownloader; import org.chromium.components.payments.PaymentManifestDownloader;
import org.chromium.components.payments.PaymentManifestParser; import org.chromium.components.payments.PaymentManifestParser;
import org.chromium.components.payments.WebAppManifestSection; import org.chromium.components.payments.WebAppManifestSection;
import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.WebContents;
import org.chromium.payments.mojom.PaymentDetailsModifier;
import org.chromium.payments.mojom.PaymentMethodData;
import org.chromium.url.URI; import org.chromium.url.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Map;
/** Tests for the native Android payment app finder. */ /** Tests for the native Android payment app finder. */
public class AndroidPaymentAppFinderUnitTest { public class AndroidPaymentAppFinderUnitTest {
...@@ -62,23 +63,52 @@ public class AndroidPaymentAppFinderUnitTest { ...@@ -62,23 +63,52 @@ public class AndroidPaymentAppFinderUnitTest {
} }
} }
private PaymentAppFactoryDelegate findApps(String[] methodNames,
PaymentManifestDownloader downloader, PaymentManifestParser parser,
PackageManagerDelegate packageManagerDelegate) {
Map<String, PaymentMethodData> methodData = new HashMap<>();
for (String methodName : methodNames) {
methodData.put(methodName, null);
}
PaymentAppFactoryParams params = Mockito.mock(PaymentAppFactoryParams.class);
Mockito.when(params.getWebContents()).thenReturn(Mockito.mock(WebContents.class));
Mockito.when(params.getId()).thenReturn("id");
Mockito.when(params.getMethodData()).thenReturn(methodData);
Mockito.when(params.getTopLevelOrigin()).thenReturn("https://chromium.org");
Mockito.when(params.getPaymentRequestOrigin()).thenReturn("https://chromium.org");
Mockito.when(params.getCertificateChain()).thenReturn(null);
Mockito.when(params.getModifiers())
.thenReturn(new HashMap<String, PaymentDetailsModifier>());
Mockito.when(params.getMayCrawl()).thenReturn(false);
PaymentAppFactoryDelegate delegate = Mockito.mock(PaymentAppFactoryDelegate.class);
Mockito.when(delegate.getParams()).thenReturn(params);
AndroidPaymentAppFinder finder =
new AndroidPaymentAppFinder(Mockito.mock(PaymentManifestWebDataService.class),
downloader, parser, packageManagerDelegate, delegate,
/*factory=*/null);
finder.bypassIsReadyToPayServiceInTest();
finder.findAndroidPaymentApps();
return delegate;
}
private void verifyNoAppsFound(PaymentAppFactoryDelegate delegate) {
Mockito.verify(delegate, Mockito.never())
.onPaymentAppCreated(Mockito.any(PaymentInstrument.class));
Mockito.verify(delegate, Mockito.never())
.onPaymentAppCreationError(Mockito.any(String.class));
Mockito.verify(delegate, Mockito.never())
.onAutofillPaymentAppCreatorAvailable(Mockito.any(AutofillPaymentAppCreator.class));
Mockito.verify(delegate).onCanMakePaymentCalculated(false);
Mockito.verify(delegate).onDoneCreatingPaymentApps(/*factory=*/null);
}
@CalledByNativeJavaTest @CalledByNativeJavaTest
public void testNoValidPaymentMethodNames() { public void testNoValidPaymentMethodNames() {
Set<String> methodNames = new HashSet<>(); verifyNoAppsFound(findApps(new String[] {"unknown-payment-method-name",
methodNames.add("unknown-payment-method-name"); "http://not.secure.payment.method.name.com", "https://"},
methodNames.add("http://not.secure.payment.method.name.com");
methodNames.add("https://"); // Invalid URI.
PaymentAppCreatedCallback callback = Mockito.mock(PaymentAppCreatedCallback.class);
AndroidPaymentAppFinder.find(Mockito.mock(WebContents.class), methodNames,
Mockito.mock(PaymentManifestWebDataService.class),
Mockito.mock(PaymentManifestDownloader.class), Mockito.mock(PaymentManifestDownloader.class),
Mockito.mock(PaymentManifestParser.class), Mockito.mock(PaymentManifestParser.class),
Mockito.mock(PackageManagerDelegate.class), callback); Mockito.mock(PackageManagerDelegate.class)));
Mockito.verify(callback, Mockito.never())
.onPaymentAppCreated(Mockito.any(PaymentApp.class));
Mockito.verify(callback).onAllPaymentAppsCreated();
} }
@CalledByNativeJavaTest @CalledByNativeJavaTest
...@@ -87,21 +117,14 @@ public class AndroidPaymentAppFinderUnitTest { ...@@ -87,21 +117,14 @@ public class AndroidPaymentAppFinderUnitTest {
Mockito.when(packageManagerDelegate.getActivitiesThatCanRespondToIntentWithMetaData( Mockito.when(packageManagerDelegate.getActivitiesThatCanRespondToIntentWithMetaData(
ArgumentMatchers.argThat(sPayIntentArgumentMatcher))) ArgumentMatchers.argThat(sPayIntentArgumentMatcher)))
.thenReturn(new ArrayList<ResolveInfo>()); .thenReturn(new ArrayList<ResolveInfo>());
Set<String> methodNames = new HashSet<>();
methodNames.add("basic-card");
PaymentAppCreatedCallback callback = Mockito.mock(PaymentAppCreatedCallback.class);
AndroidPaymentAppFinder.find(Mockito.mock(WebContents.class), methodNames, verifyNoAppsFound(
Mockito.mock(PaymentManifestWebDataService.class), findApps(new String[] {"basic-card"}, Mockito.mock(PaymentManifestDownloader.class),
Mockito.mock(PaymentManifestDownloader.class), Mockito.mock(PaymentManifestParser.class), packageManagerDelegate));
Mockito.mock(PaymentManifestParser.class), packageManagerDelegate, callback);
Mockito.verify(packageManagerDelegate, Mockito.never()) Mockito.verify(packageManagerDelegate, Mockito.never())
.getStringArrayResourceForApplication( .getStringArrayResourceForApplication(
ArgumentMatchers.any(ApplicationInfo.class), ArgumentMatchers.anyInt()); ArgumentMatchers.any(ApplicationInfo.class), ArgumentMatchers.anyInt());
Mockito.verify(callback, Mockito.never())
.onPaymentAppCreated(Mockito.any(PaymentApp.class));
Mockito.verify(callback).onAllPaymentAppsCreated();
} }
@CalledByNativeJavaTest @CalledByNativeJavaTest
...@@ -114,29 +137,23 @@ public class AndroidPaymentAppFinderUnitTest { ...@@ -114,29 +137,23 @@ public class AndroidPaymentAppFinderUnitTest {
activities.add(alicePay); activities.add(alicePay);
PackageManagerDelegate packageManagerDelegate = Mockito.mock(PackageManagerDelegate.class); PackageManagerDelegate packageManagerDelegate = Mockito.mock(PackageManagerDelegate.class);
Mockito.when(packageManagerDelegate.getAppLabel(Mockito.any(ResolveInfo.class)))
.thenReturn("A non-empty label");
Mockito.when(packageManagerDelegate.getActivitiesThatCanRespondToIntentWithMetaData( Mockito.when(packageManagerDelegate.getActivitiesThatCanRespondToIntentWithMetaData(
ArgumentMatchers.argThat(sPayIntentArgumentMatcher))) ArgumentMatchers.argThat(sPayIntentArgumentMatcher)))
.thenReturn(activities); .thenReturn(activities);
Set<String> methodNames = new HashSet<>(); verifyNoAppsFound(
methodNames.add("basic-card"); findApps(new String[] {"basic-card"}, Mockito.mock(PaymentManifestDownloader.class),
PaymentAppCreatedCallback callback = Mockito.mock(PaymentAppCreatedCallback.class); Mockito.mock(PaymentManifestParser.class), packageManagerDelegate));
AndroidPaymentAppFinder.find(Mockito.mock(WebContents.class), methodNames,
Mockito.mock(PaymentManifestWebDataService.class),
Mockito.mock(PaymentManifestDownloader.class),
Mockito.mock(PaymentManifestParser.class), packageManagerDelegate, callback);
Mockito.verify(packageManagerDelegate, Mockito.never()) Mockito.verify(packageManagerDelegate, Mockito.never())
.getStringArrayResourceForApplication( .getStringArrayResourceForApplication(
ArgumentMatchers.any(ApplicationInfo.class), ArgumentMatchers.anyInt()); ArgumentMatchers.any(ApplicationInfo.class), ArgumentMatchers.anyInt());
Mockito.verify(callback, Mockito.never())
.onPaymentAppCreated(Mockito.any(PaymentApp.class));
Mockito.verify(callback).onAllPaymentAppsCreated();
} }
@CalledByNativeJavaTest @CalledByNativeJavaTest
public void testQueryWithUnsupportedPaymentMethod() { public void testQueryWithoutLabel() {
List<ResolveInfo> activities = new ArrayList<>(); List<ResolveInfo> activities = new ArrayList<>();
ResolveInfo alicePay = new ResolveInfo(); ResolveInfo alicePay = new ResolveInfo();
alicePay.activityInfo = new ActivityInfo(); alicePay.activityInfo = new ActivityInfo();
...@@ -154,21 +171,80 @@ public class AndroidPaymentAppFinderUnitTest { ...@@ -154,21 +171,80 @@ public class AndroidPaymentAppFinderUnitTest {
ArgumentMatchers.argThat(sPayIntentArgumentMatcher))) ArgumentMatchers.argThat(sPayIntentArgumentMatcher)))
.thenReturn(activities); .thenReturn(activities);
Set<String> methodNames = new HashSet<>(); verifyNoAppsFound(
methodNames.add("basic-card"); findApps(new String[] {"basic-card"}, Mockito.mock(PaymentManifestDownloader.class),
PaymentAppCreatedCallback callback = Mockito.mock(PaymentAppCreatedCallback.class); Mockito.mock(PaymentManifestParser.class), packageManagerDelegate));
AndroidPaymentAppFinder.find(Mockito.mock(WebContents.class), methodNames, Mockito.verify(packageManagerDelegate, Mockito.never())
Mockito.mock(PaymentManifestWebDataService.class), .getStringArrayResourceForApplication(
ArgumentMatchers.any(ApplicationInfo.class), ArgumentMatchers.anyInt());
}
@CalledByNativeJavaTest
public void testQueryUnsupportedPaymentMethod() {
PackageManagerDelegate packageManagerDelegate = installPaymentApps(
new String[] {"com.alicepay.app"}, new String[] {"unsupported-payment-method"});
verifyNoAppsFound(findApps(new String[] {"unsupported-payment-method"},
Mockito.mock(PaymentManifestDownloader.class), Mockito.mock(PaymentManifestDownloader.class),
Mockito.mock(PaymentManifestParser.class), packageManagerDelegate, callback); Mockito.mock(PaymentManifestParser.class), packageManagerDelegate));
Mockito.verify(packageManagerDelegate, Mockito.never())
.getStringArrayResourceForApplication(
ArgumentMatchers.any(ApplicationInfo.class), ArgumentMatchers.anyInt());
}
private static PackageManagerDelegate installPaymentApps(
String[] packageNames, String[] methodNames) {
assert packageNames.length == methodNames.length;
List<ResolveInfo> activities = new ArrayList<>();
for (int i = 0; i < packageNames.length; i++) {
ResolveInfo alicePay = new ResolveInfo();
alicePay.activityInfo = new ActivityInfo();
alicePay.activityInfo.packageName = packageNames[i];
alicePay.activityInfo.name = packageNames[i] + ".WebPaymentActivity";
Bundle activityMetaData = new Bundle();
activityMetaData.putString(
AndroidPaymentAppFinder.META_DATA_NAME_OF_DEFAULT_PAYMENT_METHOD_NAME,
methodNames[i]);
alicePay.activityInfo.metaData = activityMetaData;
activities.add(alicePay);
}
PackageManagerDelegate packageManagerDelegate = Mockito.mock(PackageManagerDelegate.class);
Mockito.when(packageManagerDelegate.getAppLabel(Mockito.any(ResolveInfo.class)))
.thenReturn("A non-empty label");
Mockito.when(packageManagerDelegate.getActivitiesThatCanRespondToIntentWithMetaData(
ArgumentMatchers.argThat(sPayIntentArgumentMatcher)))
.thenReturn(activities);
return packageManagerDelegate;
}
@CalledByNativeJavaTest
public void testQueryDifferentPaymentMethod() {
PackageManagerDelegate packageManagerDelegate =
installPaymentApps(new String[] {"com.alicepay.app"}, new String[] {"basic-card"});
verifyNoAppsFound(findApps(new String[] {"interledger"},
Mockito.mock(PaymentManifestDownloader.class),
Mockito.mock(PaymentManifestParser.class), packageManagerDelegate));
Mockito.verify(packageManagerDelegate, Mockito.never())
.getStringArrayResourceForApplication(
ArgumentMatchers.any(ApplicationInfo.class), ArgumentMatchers.anyInt());
}
@CalledByNativeJavaTest
public void testQueryNoPaymentMethod() {
PackageManagerDelegate packageManagerDelegate =
installPaymentApps(new String[] {"com.alicepay.app"}, new String[] {"basic-card"});
verifyNoAppsFound(findApps(new String[0], Mockito.mock(PaymentManifestDownloader.class),
Mockito.mock(PaymentManifestParser.class), packageManagerDelegate));
Mockito.verify(packageManagerDelegate, Mockito.never()) Mockito.verify(packageManagerDelegate, Mockito.never())
.getStringArrayResourceForApplication( .getStringArrayResourceForApplication(
ArgumentMatchers.any(ApplicationInfo.class), ArgumentMatchers.anyInt()); ArgumentMatchers.any(ApplicationInfo.class), ArgumentMatchers.anyInt());
Mockito.verify(callback, Mockito.never())
.onPaymentAppCreated(Mockito.any(PaymentApp.class));
Mockito.verify(callback).onAllPaymentAppsCreated();
} }
@CalledByNativeJavaTest @CalledByNativeJavaTest
...@@ -220,20 +296,16 @@ public class AndroidPaymentAppFinderUnitTest { ...@@ -220,20 +296,16 @@ public class AndroidPaymentAppFinderUnitTest {
ArgumentMatchers.eq(2))) ArgumentMatchers.eq(2)))
.thenReturn(new String[] {"https://bobpay.com"}); .thenReturn(new String[] {"https://bobpay.com"});
Set<String> methodNames = new HashSet<>(); PaymentAppFactoryDelegate delegate =
methodNames.add("basic-card"); findApps(new String[] {"basic-card"}, Mockito.mock(PaymentManifestDownloader.class),
PaymentAppCreatedCallback callback = Mockito.mock(PaymentAppCreatedCallback.class); Mockito.mock(PaymentManifestParser.class), packageManagerDelegate);
AndroidPaymentAppFinder.find(Mockito.mock(WebContents.class), methodNames, Mockito.verify(delegate).onCanMakePaymentCalculated(true);
Mockito.mock(PaymentManifestWebDataService.class), Mockito.verify(delegate).onPaymentAppCreated(
Mockito.mock(PaymentManifestDownloader.class),
Mockito.mock(PaymentManifestParser.class), packageManagerDelegate, callback);
Mockito.verify(callback).onPaymentAppCreated(
ArgumentMatchers.argThat(Matches.paymentAppIdentifier("com.alicepay.app"))); ArgumentMatchers.argThat(Matches.paymentAppIdentifier("com.alicepay.app")));
Mockito.verify(callback).onPaymentAppCreated( Mockito.verify(delegate).onPaymentAppCreated(
ArgumentMatchers.argThat(Matches.paymentAppIdentifier("com.bobpay.app"))); ArgumentMatchers.argThat(Matches.paymentAppIdentifier("com.bobpay.app")));
Mockito.verify(callback).onAllPaymentAppsCreated(); Mockito.verify(delegate).onDoneCreatingPaymentApps(/*factory=*/null);
} }
@CalledByNativeJavaTest @CalledByNativeJavaTest
...@@ -328,20 +400,15 @@ public class AndroidPaymentAppFinderUnitTest { ...@@ -328,20 +400,15 @@ public class AndroidPaymentAppFinderUnitTest {
public void destroyNative() {} public void destroyNative() {}
}; };
Set<String> methodNames = new HashSet<>(); PaymentAppFactoryDelegate delegate = findApps(
methodNames.add("https://bobpay.com"); new String[] {"https://bobpay.com"}, downloader, parser, packageManagerDelegate);
PaymentAppCreatedCallback callback = Mockito.mock(PaymentAppCreatedCallback.class);
AndroidPaymentAppFinder.find(Mockito.mock(WebContents.class), methodNames,
Mockito.mock(PaymentManifestWebDataService.class), downloader, parser,
packageManagerDelegate, callback);
Mockito.verify(callback).onPaymentAppCreated( Mockito.verify(delegate).onPaymentAppCreated(
ArgumentMatchers.argThat(Matches.paymentAppIdentifier("com.bobpay.app"))); ArgumentMatchers.argThat(Matches.paymentAppIdentifier("com.bobpay.app")));
Mockito.verify(callback).onAllPaymentAppsCreated(); Mockito.verify(delegate).onDoneCreatingPaymentApps(/*factory=*/null);
} }
private static final class Matches implements ArgumentMatcher<PaymentApp> { private static final class Matches implements ArgumentMatcher<PaymentInstrument> {
private final String mExpectedAppIdentifier; private final String mExpectedAppIdentifier;
private Matches(String expectedAppIdentifier) { private Matches(String expectedAppIdentifier) {
...@@ -354,14 +421,14 @@ public class AndroidPaymentAppFinderUnitTest { ...@@ -354,14 +421,14 @@ public class AndroidPaymentAppFinderUnitTest {
* @param expectedAppIdentifier The expected app identifier to match. * @param expectedAppIdentifier The expected app identifier to match.
* @return A matcher to use in a mock expectation. * @return A matcher to use in a mock expectation.
*/ */
public static ArgumentMatcher<PaymentApp> paymentAppIdentifier( public static ArgumentMatcher<PaymentInstrument> paymentAppIdentifier(
String expectedAppIdentifier) { String expectedAppIdentifier) {
return new Matches(expectedAppIdentifier); return new Matches(expectedAppIdentifier);
} }
@Override @Override
public boolean matches(PaymentApp app) { public boolean matches(PaymentInstrument app) {
return app.getAppIdentifier().equals(mExpectedAppIdentifier); return app.getIdentifier().equals(mExpectedAppIdentifier);
} }
} }
} }
file://components/payments/OWNERS
# COMPONENT: UI>Browser>Payments
...@@ -103,7 +103,6 @@ const base::Feature* kFeaturesExposedToJava[] = { ...@@ -103,7 +103,6 @@ const base::Feature* kFeaturesExposedToJava[] = {
&kAndroidNightModeTabReparenting, &kAndroidNightModeTabReparenting,
&kAndroidPayIntegrationV1, &kAndroidPayIntegrationV1,
&kAndroidPayIntegrationV2, &kAndroidPayIntegrationV2,
&kAndroidPaymentApps,
&kAndroidSearchEngineChoiceNotification, &kAndroidSearchEngineChoiceNotification,
&kAndroidSetupSearchEngine, &kAndroidSetupSearchEngine,
&kAndroidSiteSettingsUIRefresh, &kAndroidSiteSettingsUIRefresh,
...@@ -299,10 +298,6 @@ const base::Feature kAllowRemoteContextForNotifications{ ...@@ -299,10 +298,6 @@ const base::Feature kAllowRemoteContextForNotifications{
const base::Feature kAndroidPayIntegrationV2{"AndroidPayIntegrationV2", const base::Feature kAndroidPayIntegrationV2{"AndroidPayIntegrationV2",
base::FEATURE_ENABLED_BY_DEFAULT}; base::FEATURE_ENABLED_BY_DEFAULT};
// TODO(rouslan): Remove this.
const base::Feature kAndroidPaymentApps{"AndroidPaymentApps",
base::FEATURE_ENABLED_BY_DEFAULT};
const base::Feature kAndroidSearchEngineChoiceNotification{ const base::Feature kAndroidSearchEngineChoiceNotification{
"AndroidSearchEngineChoiceNotification", base::FEATURE_ENABLED_BY_DEFAULT}; "AndroidSearchEngineChoiceNotification", base::FEATURE_ENABLED_BY_DEFAULT};
......
...@@ -173,7 +173,6 @@ public abstract class ChromeFeatureList { ...@@ -173,7 +173,6 @@ public abstract class ChromeFeatureList {
"AndroidNightModeTabReparenting"; "AndroidNightModeTabReparenting";
public static final String ANDROID_PAY_INTEGRATION_V1 = "AndroidPayIntegrationV1"; public static final String ANDROID_PAY_INTEGRATION_V1 = "AndroidPayIntegrationV1";
public static final String ANDROID_PAY_INTEGRATION_V2 = "AndroidPayIntegrationV2"; public static final String ANDROID_PAY_INTEGRATION_V2 = "AndroidPayIntegrationV2";
public static final String ANDROID_PAYMENT_APPS = "AndroidPaymentApps";
public static final String ANDROID_SEARCH_ENGINE_CHOICE_NOTIFICATION = public static final String ANDROID_SEARCH_ENGINE_CHOICE_NOTIFICATION =
"AndroidSearchEngineChoiceNotification"; "AndroidSearchEngineChoiceNotification";
public static final String ANDROID_SETUP_SEARCH_ENGINE = "AndroidSetupSearchEngine"; public static final String ANDROID_SETUP_SEARCH_ENGINE = "AndroidSetupSearchEngine";
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment