Chromium Code Reviews| Index: chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFactory.java |
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFactory.java |
| index 9eaf4575f4403532824ff6b0bee2559d5b482dc4..15a5d39326a3b864516dfd7c0cb1a081692f0eb1 100644 |
| --- a/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFactory.java |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFactory.java |
| @@ -4,7 +4,6 @@ |
| package org.chromium.chrome.browser.payments; |
| -import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| @@ -13,83 +12,212 @@ import android.net.Uri; |
| import android.util.Pair; |
| import org.chromium.base.ContextUtils; |
| -import org.chromium.chrome.browser.ChromeActivity; |
| +import org.chromium.base.annotations.SuppressFBWarnings; |
| import org.chromium.chrome.browser.UrlConstants; |
| import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedCallback; |
| import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppFactoryAddition; |
| +import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentManifestParser; |
| +import org.chromium.chrome.browser.payments.PaymentManifestVerifier.ManifestVerifyCallback; |
| import org.chromium.content_public.browser.WebContents; |
| +import java.net.MalformedURLException; |
| +import java.net.URL; |
| +import java.util.ArrayList; |
| import java.util.HashMap; |
| +import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| /** Builds instances of payment apps based on installed third party Android payment apps. */ |
| public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition { |
| - private static final String ACTION_IS_READY_TO_PAY = |
| - "org.chromium.intent.action.IS_READY_TO_PAY"; |
| - |
| - /** The action name for the Pay Basic-card Intent. */ |
| - private static final String ACTION_PAY_BASIC_CARD = "org.chromium.intent.action.PAY_BASIC_CARD"; |
| + @Override |
| + public void create(WebContents webContents, Set<String> methods, PaymentManifestParser parser, |
| + PaymentAppCreatedCallback callback) { |
| + new PaymentAppFinder( |
| + webContents, methods, new PaymentManifestDownloader(webContents), parser, callback) |
| + .find(); |
| + } |
| /** |
| - * The basic-card payment method name used by merchant and defined by W3C: |
| - * https://w3c.github.io/webpayments-methods-card/#method-id |
| + * Finds installed native Android payment apps and verifies their signatures according to the |
| + * payment method manifests. The manifests are located based on the payment method name, which |
| + * is a URL that starts with "https://". The "basic-card" payment method is an exception: it's a |
| + * common payment method that does not have a manifest and can be used by any payment app. |
| */ |
| - private static final String BASIC_CARD_PAYMENT_METHOD = "basic-card"; |
| + @SuppressFBWarnings(value = {"DMI_COLLECTION_OF_URLS"}, justification = "At most 10 elements") |
| + private static class PaymentAppFinder implements ManifestVerifyCallback { |
| + /** The name of the intent for the service to check whether an app is ready to pay. */ |
| + private static final String ACTION_IS_READY_TO_PAY = |
| + "org.chromium.intent.action.IS_READY_TO_PAY"; |
| - @Override |
| - public void create(WebContents webContents, Set<String> methods, |
| - PaymentAppCreatedCallback callback) { |
| - Context context = ChromeActivity.fromWebContents(webContents); |
| - if (context == null) { |
| - callback.onAllPaymentAppsCreated(); |
| - return; |
| + /** The name of the intent for the action of paying using "basic-card" method. */ |
| + private static final String ACTION_PAY_BASIC_CARD = |
| + "org.chromium.intent.action.PAY_BASIC_CARD"; |
| + |
| + /** |
| + * The basic-card payment method name used by merchant and defined by W3C: |
| + * https://w3c.github.io/webpayments-methods-card/#method-id |
| + */ |
| + private static final String BASIC_CARD_PAYMENT_METHOD = "basic-card"; |
| + |
| + /** The maximum number of payment method manifests to download. */ |
| + private static final int MAX_NUMBER_OF_MANIFESTS = 10; |
| + |
| + private final WebContents mWebContents; |
| + private final PaymentManifestDownloader mDownloader; |
| + private final PaymentManifestParser mParser; |
| + private final PaymentAppCreatedCallback mCallback; |
| + |
| + /** Whether "basic-card" supporting payment apps should be queried. */ |
| + private final boolean mQueryBasicCard; |
| + |
| + /** |
| + * A map of payment method names to the list of unverified (yet) Android apps that claim to |
| + * handle these methods. Example payment method names in this data structure: |
| + * "https://bobpay.com", "https://android.com/pay". Basic card is excluded. |
| + */ |
| + private final Map<URL, Set<ResolveInfo>> mPendingApps; |
| + |
| + /** A map of Android package name to the payment app. */ |
| + private final Map<String, AndroidPaymentApp> mResult; |
| + |
| + private final Set<URL> mPaymentMethods; |
| + |
| + /** |
| + * Builds a native Android payment app finder. |
| + * |
| + * @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 downloader The payment manifest downloader. |
| + * @param parser The payment manifest parser. |
| + * @param callback The asynchronous callback to be invoked (on the UI thread) when all |
| + * Android payment apps have been found. |
| + */ |
| + public PaymentAppFinder(WebContents webContents, Set<String> methods, |
| + PaymentManifestDownloader downloader, PaymentManifestParser parser, |
| + PaymentAppCreatedCallback callback) { |
| + mWebContents = webContents; |
| + mDownloader = downloader; |
| + mParser = parser; |
| + mCallback = callback; |
| + mQueryBasicCard = methods.contains(BASIC_CARD_PAYMENT_METHOD); |
| + mPendingApps = new HashMap<>(); |
| + mResult = new HashMap<>(); |
| + |
| + mPaymentMethods = new HashSet<>(); |
| + for (String method : methods) { |
| + assert method != null; |
| + if (method.startsWith(UrlConstants.HTTPS_URL_PREFIX)) { |
| + URL url = null; |
| + try { |
| + url = new URL(method); |
|
xunjieli
2017/02/24 18:18:30
Is it possible to avoid using java.net.URL?
This d
please use gerrit instead
2017/03/03 03:11:17
Done.
|
| + } catch (MalformedURLException e) { |
| + continue; |
| + } |
| + assert UrlConstants.HTTPS_SCHEME.equals(url.getProtocol()); |
| + mPaymentMethods.add(url); |
| + } |
| + } |
| } |
| - Map<String, AndroidPaymentApp> installedApps = new HashMap<>(); |
| - PackageManager pm = context.getPackageManager(); |
| - Intent payIntent = new Intent(); |
| + /** Looks for native Android payment apps. */ |
| + public void find() { |
| + PackageManager pm = ContextUtils.getApplicationContext().getPackageManager(); |
| + Intent payIntent = new Intent(); |
| + payIntent.setAction(AndroidPaymentApp.ACTION_PAY); |
| + |
| + List<PaymentManifestVerifier> verifiers = new ArrayList<>(); |
| + for (URL methodName : mPaymentMethods) { |
| + payIntent.setData(Uri.parse(methodName.toString())); |
| + List<ResolveInfo> apps = pm.queryIntentActivities(payIntent, 0); |
| + if (apps.isEmpty()) continue; |
| + verifiers.add(new PaymentManifestVerifier( |
| + pm, methodName, apps, mDownloader, mParser, this)); |
| + mPendingApps.put(methodName, new HashSet<>(apps)); |
| + if (verifiers.size() == MAX_NUMBER_OF_MANIFESTS) break; |
| + } |
| - for (String methodName : methods) { |
| - if (methodName.startsWith(UrlConstants.HTTPS_URL_PREFIX)) { |
| - payIntent.setAction(AndroidPaymentApp.ACTION_PAY); |
| - payIntent.setData(Uri.parse(methodName)); |
| - } else if (methodName.equals(BASIC_CARD_PAYMENT_METHOD)) { |
| + if (mQueryBasicCard) { |
| payIntent.setAction(ACTION_PAY_BASIC_CARD); |
| payIntent.setData(null); |
| - } else { |
| - continue; |
| + List<ResolveInfo> apps = pm.queryIntentActivities(payIntent, 0); |
| + for (int i = 0; i < apps.size(); i++) { |
| + onValidPaymentApp(BASIC_CARD_PAYMENT_METHOD, apps.get(i)); |
| + } |
| } |
| - List<ResolveInfo> matches = pm.queryIntentActivities(payIntent, 0); |
| - for (int i = 0; i < matches.size(); i++) { |
| - ResolveInfo match = matches.get(i); |
| - String packageName = match.activityInfo.packageName; |
| - // Do not recommend disabled apps. |
| - if (!PaymentPreferencesUtil.isAndroidPaymentAppEnabled(packageName)) continue; |
| - AndroidPaymentApp installedApp = installedApps.get(packageName); |
| - if (installedApp == null) { |
| - CharSequence label = match.loadLabel(pm); |
| - installedApp = |
| - new AndroidPaymentApp(webContents, packageName, match.activityInfo.name, |
| - label == null ? "" : label.toString(), match.loadIcon(pm)); |
| - callback.onPaymentAppCreated(installedApp); |
| - installedApps.put(packageName, installedApp); |
| - } |
| - installedApp.addMethodName(methodName); |
| + if (verifiers.isEmpty()) { |
| + onSearchFinished(); |
| + return; |
| + } |
| + |
| + for (int i = 0; i < verifiers.size(); i++) { |
| + verifiers.get(i).verify(); |
| + } |
| + } |
| + |
| + @Override |
| + public void onValidPaymentApp(URL methodName, ResolveInfo resolveInfo) { |
| + onValidPaymentApp(methodName.toString(), resolveInfo); |
| + removePendingApp(methodName, resolveInfo); |
| + } |
| + |
| + /** Same as above, but also works for non-URL method names, e.g., "basic-card". */ |
| + private void onValidPaymentApp(String methodName, ResolveInfo resolveInfo) { |
| + PackageManager pm = ContextUtils.getApplicationContext().getPackageManager(); |
| + String packageName = resolveInfo.activityInfo.packageName; |
| + AndroidPaymentApp app = mResult.get(packageName); |
| + if (app == null) { |
| + CharSequence label = resolveInfo.loadLabel(pm); |
| + app = new AndroidPaymentApp(mWebContents, packageName, |
| + resolveInfo.activityInfo.name, label == null ? "" : label.toString(), |
| + resolveInfo.loadIcon(pm)); |
| + mResult.put(packageName, app); |
| } |
| + app.addMethodName(methodName); |
| } |
| - List<ResolveInfo> matches = pm.queryIntentServices(new Intent(ACTION_IS_READY_TO_PAY), 0); |
| - for (int i = 0; i < matches.size(); i++) { |
| - ResolveInfo match = matches.get(i); |
| - String packageName = match.serviceInfo.packageName; |
| - AndroidPaymentApp installedApp = installedApps.get(packageName); |
| - if (installedApp != null) installedApp.setIsReadyToPayAction(match.serviceInfo.name); |
| + @Override |
| + public void onInvalidPaymentApp(URL methodName, ResolveInfo resolveInfo) { |
| + removePendingApp(methodName, resolveInfo); |
| } |
| - callback.onAllPaymentAppsCreated(); |
| + /** Removes the (method, app) pair from the list of pending information to be verified. */ |
| + private void removePendingApp(URL methodName, ResolveInfo resolveInfo) { |
| + Set<ResolveInfo> pendingAppsForMethod = mPendingApps.get(methodName); |
| + pendingAppsForMethod.remove(resolveInfo); |
| + if (pendingAppsForMethod.isEmpty()) mPendingApps.remove(methodName); |
| + if (mPendingApps.isEmpty()) onSearchFinished(); |
| + } |
| + |
| + @Override |
| + public void onInvalidManifest(URL methodName) { |
| + mPendingApps.remove(methodName); |
| + if (mPendingApps.isEmpty()) onSearchFinished(); |
| + } |
| + |
| + /** |
| + * Checks for IS_READY_TO_PAY service in each valid payment app and returns the valid apps |
| + * to the caller. Called when finished verifying all payment methods and apps. |
| + */ |
| + private void onSearchFinished() { |
| + PackageManager pm = ContextUtils.getApplicationContext().getPackageManager(); |
| + List<ResolveInfo> resolveInfos = |
| + pm.queryIntentServices(new Intent(ACTION_IS_READY_TO_PAY), 0); |
| + for (int i = 0; i < resolveInfos.size(); i++) { |
| + ResolveInfo resolveInfo = resolveInfos.get(i); |
| + AndroidPaymentApp app = mResult.get(resolveInfo.serviceInfo.packageName); |
| + if (app != null) app.setIsReadyToPayAction(resolveInfo.serviceInfo.name); |
| + } |
| + |
| + for (Map.Entry<String, AndroidPaymentApp> entry : mResult.entrySet()) { |
| + mCallback.onPaymentAppCreated(entry.getValue()); |
| + } |
| + |
| + mCallback.onAllPaymentAppsCreated(); |
| + } |
| } |
| /** |