Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chrome.browser.payments; | 5 package org.chromium.chrome.browser.payments; |
| 6 | 6 |
| 7 import android.content.Context; | |
| 8 import android.content.Intent; | 7 import android.content.Intent; |
| 9 import android.content.pm.PackageManager; | 8 import android.content.pm.PackageManager; |
| 10 import android.content.pm.ResolveInfo; | 9 import android.content.pm.ResolveInfo; |
| 11 import android.graphics.drawable.Drawable; | 10 import android.graphics.drawable.Drawable; |
| 12 import android.net.Uri; | 11 import android.net.Uri; |
| 13 import android.util.Pair; | 12 import android.util.Pair; |
| 14 | 13 |
| 15 import org.chromium.base.ContextUtils; | 14 import org.chromium.base.ContextUtils; |
| 16 import org.chromium.chrome.browser.ChromeActivity; | |
| 17 import org.chromium.chrome.browser.UrlConstants; | 15 import org.chromium.chrome.browser.UrlConstants; |
| 18 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedC allback; | 16 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedC allback; |
| 19 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppFactoryA ddition; | 17 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppFactoryA ddition; |
| 18 import org.chromium.chrome.browser.payments.PaymentManifestVerifier.ManifestVeri fyCallback; | |
| 20 import org.chromium.content_public.browser.WebContents; | 19 import org.chromium.content_public.browser.WebContents; |
| 21 | 20 |
| 21 import java.net.URI; | |
| 22 import java.net.URISyntaxException; | |
| 23 import java.util.ArrayList; | |
| 22 import java.util.HashMap; | 24 import java.util.HashMap; |
| 25 import java.util.HashSet; | |
| 23 import java.util.List; | 26 import java.util.List; |
| 24 import java.util.Map; | 27 import java.util.Map; |
| 25 import java.util.Set; | 28 import java.util.Set; |
| 26 | 29 |
| 27 /** Builds instances of payment apps based on installed third party Android paym ent apps. */ | 30 /** Builds instances of payment apps based on installed third party Android paym ent apps. */ |
| 28 public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition { | 31 public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition { |
| 29 private static final String ACTION_IS_READY_TO_PAY = | 32 @Override |
| 30 "org.chromium.intent.action.IS_READY_TO_PAY"; | 33 public void create( |
| 31 | 34 WebContents webContents, Set<String> methods, PaymentAppCreatedCallb ack callback) { |
| 32 /** The action name for the Pay Basic-card Intent. */ | 35 new PaymentAppFinder(webContents, methods, callback).find(); |
|
Ted C
2017/03/03 17:44:45
A slightly nicer pattern IMO would just be to have
please use gerrit instead
2017/03/09 18:05:33
Done.
| |
| 33 private static final String ACTION_PAY_BASIC_CARD = "org.chromium.intent.act ion.PAY_BASIC_CARD"; | 36 } |
| 34 | 37 |
| 35 /** | 38 /** |
| 36 * The basic-card payment method name used by merchant and defined by W3C: | 39 * Finds installed native Android payment apps and verifies their signatures according to the |
| 37 * https://w3c.github.io/webpayments-methods-card/#method-id | 40 * payment method manifests. The manifests are located based on the payment method name, which |
| 41 * is a URI that starts with "https://". The "basic-card" payment method is an exception: it's a | |
| 42 * common payment method that does not have a manifest and can be used by an y payment app. | |
| 38 */ | 43 */ |
| 39 private static final String BASIC_CARD_PAYMENT_METHOD = "basic-card"; | 44 private static class PaymentAppFinder implements ManifestVerifyCallback { |
|
Ted C
2017/03/03 17:44:45
I'd pull this out to a separate class (package pro
please use gerrit instead
2017/03/09 18:05:33
Done.
| |
| 40 | 45 /** The name of the intent for the service to check whether an app is re ady to pay. */ |
| 41 @Override | 46 private static final String ACTION_IS_READY_TO_PAY = |
| 42 public void create(WebContents webContents, Set<String> methods, | 47 "org.chromium.intent.action.IS_READY_TO_PAY"; |
| 43 PaymentAppCreatedCallback callback) { | 48 |
| 44 Context context = ChromeActivity.fromWebContents(webContents); | 49 /** The name of the intent for the action of paying using "basic-card" m ethod. */ |
| 45 if (context == null) { | 50 private static final String ACTION_PAY_BASIC_CARD = |
| 46 callback.onAllPaymentAppsCreated(); | 51 "org.chromium.intent.action.PAY_BASIC_CARD"; |
| 47 return; | 52 |
| 48 } | 53 /** |
| 49 | 54 * The basic-card payment method name used by merchant and defined by W3 C: |
| 50 Map<String, AndroidPaymentApp> installedApps = new HashMap<>(); | 55 * https://w3c.github.io/webpayments-methods-card/#method-id |
| 51 PackageManager pm = context.getPackageManager(); | 56 */ |
| 52 Intent payIntent = new Intent(); | 57 private static final String BASIC_CARD_PAYMENT_METHOD = "basic-card"; |
| 53 | 58 |
| 54 for (String methodName : methods) { | 59 /** The maximum number of payment method manifests to download. */ |
| 55 if (methodName.startsWith(UrlConstants.HTTPS_URL_PREFIX)) { | 60 private static final int MAX_NUMBER_OF_MANIFESTS = 10; |
| 56 payIntent.setAction(AndroidPaymentApp.ACTION_PAY); | 61 |
| 57 payIntent.setData(Uri.parse(methodName)); | 62 private final WebContents mWebContents; |
| 58 } else if (methodName.equals(BASIC_CARD_PAYMENT_METHOD)) { | 63 private final PaymentAppCreatedCallback mCallback; |
| 64 | |
| 65 /** Whether "basic-card" supporting payment apps should be queried. */ | |
| 66 private final boolean mQueryBasicCard; | |
| 67 | |
| 68 /** | |
| 69 * A map of payment method names to the list of unverified (yet) Android apps that claim to | |
|
Ted C
2017/03/03 17:44:45
nit of nits, I would put the (yet) before unverifi
please use gerrit instead
2017/03/09 18:05:32
Done.
| |
| 70 * handle these methods. Example payment method names in this data struc ture: | |
| 71 * "https://bobpay.com", "https://android.com/pay". Basic card is exclud ed. | |
| 72 */ | |
| 73 private final Map<URI, Set<ResolveInfo>> mPendingApps; | |
| 74 | |
| 75 /** A map of Android package name to the payment app. */ | |
| 76 private final Map<String, AndroidPaymentApp> mResult; | |
| 77 | |
| 78 private final Set<URI> mPaymentMethods; | |
| 79 | |
| 80 private PaymentManifestParser mParser; | |
| 81 | |
| 82 /** | |
| 83 * Builds a native Android payment app finder. | |
| 84 * | |
| 85 * @param webContents The web contents that invoked the web payments API . | |
| 86 * @param methods The list of payment methods requested by the merch ant. For example, | |
| 87 * "https://bobpay.com", "https://android.com/pay", " basic-card". | |
| 88 * @param callback The asynchronous callback to be invoked (on the UI thread) when all | |
| 89 * Android payment apps have been found. | |
| 90 */ | |
| 91 public PaymentAppFinder( | |
| 92 WebContents webContents, Set<String> methods, PaymentAppCreatedC allback callback) { | |
| 93 mWebContents = webContents; | |
| 94 mCallback = callback; | |
| 95 mQueryBasicCard = methods.contains(BASIC_CARD_PAYMENT_METHOD); | |
| 96 mPendingApps = new HashMap<>(); | |
| 97 mResult = new HashMap<>(); | |
| 98 | |
| 99 mPaymentMethods = new HashSet<>(); | |
| 100 for (String method : methods) { | |
| 101 assert method != null; | |
| 102 if (method.startsWith(UrlConstants.HTTPS_URL_PREFIX)) { | |
|
Ted C
2017/03/03 17:44:45
I would do
if (!method.startsWith...) continue;
please use gerrit instead
2017/03/09 18:05:33
Done.
| |
| 103 URI uri; | |
| 104 try { | |
| 105 // Don't use java.net.URL, because it performs a synchro nous DNS lookup in | |
| 106 // the constructor. | |
| 107 uri = new URI(method); | |
| 108 } catch (URISyntaxException e) { | |
| 109 continue; | |
| 110 } | |
| 111 if (uri.isAbsolute() && UrlConstants.HTTPS_SCHEME.equals(uri .getScheme())) { | |
| 112 mPaymentMethods.add(uri); | |
| 113 } | |
| 114 } | |
| 115 } | |
| 116 } | |
| 117 | |
| 118 /** Looks for native Android payment apps. */ | |
| 119 public void find() { | |
| 120 PackageManager pm = ContextUtils.getApplicationContext().getPackageM anager(); | |
| 121 Intent payIntent = new Intent(); | |
| 122 payIntent.setAction(AndroidPaymentApp.ACTION_PAY); | |
| 123 | |
| 124 List<PaymentManifestVerifier> verifiers = new ArrayList<>(); | |
| 125 for (URI methodName : mPaymentMethods) { | |
| 126 payIntent.setData(Uri.parse(methodName.toString())); | |
| 127 List<ResolveInfo> apps = pm.queryIntentActivities(payIntent, 0); | |
|
Ted C
2017/03/03 17:44:45
you might need a strictmode exemption for this (cr
please use gerrit instead
2017/03/09 18:05:32
Moved to async task.
please use gerrit instead
2017/03/09 18:13:15
Eh, I did not, actually. Going to add an exemption
| |
| 128 if (apps.isEmpty()) continue; | |
| 129 | |
| 130 if (mParser == null) { | |
| 131 mParser = new PaymentManifestParser(); | |
| 132 mParser.startUtilityProcess(); | |
|
Ted C
2017/03/03 17:44:45
might be covered later, but do you do anything to
please use gerrit instead
2017/03/09 18:05:32
Yep, dead parser process causes all consequent req
| |
| 133 } | |
| 134 | |
| 135 verifiers.add( | |
| 136 new PaymentManifestVerifier(mWebContents, methodName, ap ps, mParser, this)); | |
| 137 mPendingApps.put(methodName, new HashSet<>(apps)); | |
| 138 if (verifiers.size() == MAX_NUMBER_OF_MANIFESTS) break; | |
|
Ted C
2017/03/03 17:44:45
maybe log something here that we've exceeded our m
please use gerrit instead
2017/03/09 18:05:33
Done.
| |
| 139 } | |
| 140 | |
| 141 if (mQueryBasicCard) { | |
| 59 payIntent.setAction(ACTION_PAY_BASIC_CARD); | 142 payIntent.setAction(ACTION_PAY_BASIC_CARD); |
| 60 payIntent.setData(null); | 143 payIntent.setData(null); |
| 61 } else { | 144 List<ResolveInfo> apps = pm.queryIntentActivities(payIntent, 0); |
| 62 continue; | 145 for (int i = 0; i < apps.size(); i++) { |
| 63 } | 146 onValidPaymentApp(BASIC_CARD_PAYMENT_METHOD, apps.get(i)); |
| 64 | |
| 65 List<ResolveInfo> matches = pm.queryIntentActivities(payIntent, 0); | |
| 66 for (int i = 0; i < matches.size(); i++) { | |
| 67 ResolveInfo match = matches.get(i); | |
| 68 String packageName = match.activityInfo.packageName; | |
| 69 // Do not recommend disabled apps. | |
| 70 if (!PaymentPreferencesUtil.isAndroidPaymentAppEnabled(packageNa me)) continue; | |
| 71 AndroidPaymentApp installedApp = installedApps.get(packageName); | |
| 72 if (installedApp == null) { | |
| 73 CharSequence label = match.loadLabel(pm); | |
| 74 installedApp = | |
| 75 new AndroidPaymentApp(webContents, packageName, matc h.activityInfo.name, | |
| 76 label == null ? "" : label.toString(), match .loadIcon(pm)); | |
| 77 callback.onPaymentAppCreated(installedApp); | |
| 78 installedApps.put(packageName, installedApp); | |
| 79 } | 147 } |
| 80 installedApp.addMethodName(methodName); | 148 } |
| 81 } | 149 |
| 82 } | 150 if (verifiers.isEmpty()) { |
| 83 | 151 onSearchFinished(); |
| 84 List<ResolveInfo> matches = pm.queryIntentServices(new Intent(ACTION_IS_ READY_TO_PAY), 0); | 152 return; |
| 85 for (int i = 0; i < matches.size(); i++) { | 153 } |
| 86 ResolveInfo match = matches.get(i); | 154 |
| 87 String packageName = match.serviceInfo.packageName; | 155 for (int i = 0; i < verifiers.size(); i++) { |
| 88 AndroidPaymentApp installedApp = installedApps.get(packageName); | 156 verifiers.get(i).verify(); |
| 89 if (installedApp != null) installedApp.setIsReadyToPayAction(match.s erviceInfo.name); | 157 } |
| 90 } | 158 } |
| 91 | 159 |
| 92 callback.onAllPaymentAppsCreated(); | 160 @Override |
| 161 public void onValidPaymentApp(URI methodName, ResolveInfo resolveInfo) { | |
| 162 onValidPaymentApp(methodName.toString(), resolveInfo); | |
| 163 removePendingApp(methodName, resolveInfo); | |
| 164 } | |
| 165 | |
| 166 /** Same as above, but also works for non-URI method names, e.g., "basic -card". */ | |
| 167 private void onValidPaymentApp(String methodName, ResolveInfo resolveInf o) { | |
| 168 PackageManager pm = ContextUtils.getApplicationContext().getPackageM anager(); | |
| 169 String packageName = resolveInfo.activityInfo.packageName; | |
| 170 AndroidPaymentApp app = mResult.get(packageName); | |
| 171 if (app == null) { | |
| 172 CharSequence label = resolveInfo.loadLabel(pm); | |
|
Ted C
2017/03/03 17:44:45
any reason we don't force people to have a valid l
please use gerrit instead
2017/03/09 18:05:32
Forcing now.
| |
| 173 app = new AndroidPaymentApp(mWebContents, packageName, | |
| 174 resolveInfo.activityInfo.name, label == null ? "" : labe l.toString(), | |
| 175 resolveInfo.loadIcon(pm)); | |
| 176 mResult.put(packageName, app); | |
| 177 } | |
| 178 app.addMethodName(methodName); | |
| 179 } | |
| 180 | |
| 181 @Override | |
| 182 public void onInvalidPaymentApp(URI methodName, ResolveInfo resolveInfo) { | |
| 183 removePendingApp(methodName, resolveInfo); | |
| 184 } | |
| 185 | |
| 186 /** Removes the (method, app) pair from the list of pending information to be verified. */ | |
| 187 private void removePendingApp(URI methodName, ResolveInfo resolveInfo) { | |
| 188 Set<ResolveInfo> pendingAppsForMethod = mPendingApps.get(methodName) ; | |
| 189 pendingAppsForMethod.remove(resolveInfo); | |
| 190 if (pendingAppsForMethod.isEmpty()) mPendingApps.remove(methodName); | |
| 191 if (mPendingApps.isEmpty()) onSearchFinished(); | |
| 192 } | |
| 193 | |
| 194 @Override | |
| 195 public void onInvalidManifest(URI methodName) { | |
| 196 mPendingApps.remove(methodName); | |
| 197 if (mPendingApps.isEmpty()) onSearchFinished(); | |
| 198 } | |
| 199 | |
| 200 /** | |
| 201 * Checks for IS_READY_TO_PAY service in each valid payment app and retu rns the valid apps | |
| 202 * to the caller. Called when finished verifying all payment methods and apps. | |
| 203 */ | |
| 204 private void onSearchFinished() { | |
| 205 if (mParser != null) { | |
| 206 mParser.stopUtilityProcess(); | |
| 207 mParser = null; | |
| 208 } | |
| 209 | |
|
Ted C
2017/03/03 17:44:45
for extra precaution, i'd add assert mPendingApps.
please use gerrit instead
2017/03/09 18:05:32
Done.
| |
| 210 PackageManager pm = ContextUtils.getApplicationContext().getPackageM anager(); | |
| 211 List<ResolveInfo> resolveInfos = | |
| 212 pm.queryIntentServices(new Intent(ACTION_IS_READY_TO_PAY), 0 ); | |
| 213 for (int i = 0; i < resolveInfos.size(); i++) { | |
| 214 ResolveInfo resolveInfo = resolveInfos.get(i); | |
| 215 AndroidPaymentApp app = mResult.get(resolveInfo.serviceInfo.pack ageName); | |
| 216 if (app != null) app.setIsReadyToPayAction(resolveInfo.serviceIn fo.name); | |
| 217 } | |
| 218 | |
| 219 for (Map.Entry<String, AndroidPaymentApp> entry : mResult.entrySet() ) { | |
| 220 mCallback.onPaymentAppCreated(entry.getValue()); | |
| 221 } | |
| 222 | |
| 223 mCallback.onAllPaymentAppsCreated(); | |
| 224 } | |
| 93 } | 225 } |
| 94 | 226 |
| 95 /** | 227 /** |
| 96 * Checks whether there are Android payment apps on device. | 228 * Checks whether there are Android payment apps on device. |
| 97 * | 229 * |
| 98 * @return True if there are Android payment apps on device. | 230 * @return True if there are Android payment apps on device. |
| 99 */ | 231 */ |
| 100 public static boolean hasAndroidPaymentApps() { | 232 public static boolean hasAndroidPaymentApps() { |
| 101 PackageManager pm = ContextUtils.getApplicationContext().getPackageManag er(); | 233 PackageManager pm = ContextUtils.getApplicationContext().getPackageManag er(); |
| 102 // Note that all Android payment apps must support org.chromium.intent.a ction.PAY action | 234 // Note that all Android payment apps must support org.chromium.intent.a ction.PAY action |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 121 | 253 |
| 122 for (ResolveInfo match : matches) { | 254 for (ResolveInfo match : matches) { |
| 123 Pair<String, Drawable> appInfo = | 255 Pair<String, Drawable> appInfo = |
| 124 new Pair<>(match.loadLabel(pm).toString(), match.loadIcon(pm )); | 256 new Pair<>(match.loadLabel(pm).toString(), match.loadIcon(pm )); |
| 125 paymentAppsInfo.put(match.activityInfo.packageName, appInfo); | 257 paymentAppsInfo.put(match.activityInfo.packageName, appInfo); |
| 126 } | 258 } |
| 127 | 259 |
| 128 return paymentAppsInfo; | 260 return paymentAppsInfo; |
| 129 } | 261 } |
| 130 } | 262 } |
| OLD | NEW |