Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(14)

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFactory.java

Issue 2645813006: Download web payment manifests. (Closed)
Patch Set: "basic-card" robustness Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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; 7 import android.content.Context;
8 import android.content.Intent; 8 import android.content.Intent;
9 import android.content.pm.PackageInfo;
9 import android.content.pm.PackageManager; 10 import android.content.pm.PackageManager;
11 import android.content.pm.PackageManager.NameNotFoundException;
10 import android.content.pm.ResolveInfo; 12 import android.content.pm.ResolveInfo;
13 import android.content.pm.Signature;
11 import android.net.Uri; 14 import android.net.Uri;
12 15
16 import org.chromium.chrome.browser.payments.ManifestDownloader.ManifestDownloadC allback;
17 import org.chromium.chrome.browser.payments.ManifestParser.Manifest;
18 import org.chromium.chrome.browser.payments.ManifestParser.ManifestParseCallback ;
13 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedC allback; 19 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedC allback;
14 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppFactoryA ddition; 20 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppFactoryA ddition;
15 import org.chromium.content_public.browser.WebContents; 21 import org.chromium.content_public.browser.WebContents;
16 22
23 import java.util.ArrayList;
17 import java.util.HashMap; 24 import java.util.HashMap;
25 import java.util.HashSet;
18 import java.util.List; 26 import java.util.List;
19 import java.util.Map; 27 import java.util.Map;
20 import java.util.Set; 28 import java.util.Set;
21 29
22 /** 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. */
23 public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition { 31 public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition {
24 private static final String ACTION_IS_READY_TO_PAY =
25 "org.chromium.intent.action.IS_READY_TO_PAY";
26 private static final String METHOD_PREFIX = "https://";
27
28 /** The action name for the Pay Basic-card Intent. */
29 private static final String ACTION_PAY_BASIC_CARD = "org.chromium.intent.act ion.PAY_BASIC_CARD";
30
31 /**
32 * The basic-card payment method name used by merchant and defined by W3C:
33 * https://w3c.github.io/webpayments-methods-card/#method-id
34 */
35 private static final String BASIC_CARD_PAYMENT_METHOD = "basic-card";
36
37 @Override 32 @Override
38 public void create(Context context, WebContents webContents, Set<String> met hods, 33 public void create(Context context, WebContents webContents, Set<String> met hods,
39 PaymentAppCreatedCallback callback) { 34 PaymentAppCreatedCallback callback) {
40 Map<String, AndroidPaymentApp> installedApps = new HashMap<>(); 35 new PaymentAppFinder(context, webContents, methods, callback).find();
41 PackageManager pm = context.getPackageManager(); 36 }
42 Intent payIntent = new Intent(); 37
43 38 /**
44 for (String methodName : methods) { 39 * Finds installed native Android payment apps and verifies their signatures according to the
45 if (methodName.startsWith(METHOD_PREFIX)) { 40 * payment method manifests. The "basic-card" payment method is an exception : it's a common
46 payIntent.setAction(AndroidPaymentApp.ACTION_PAY); 41 * payment method that can be used by any payment app.
42 */
43 private static class PaymentAppFinder {
44 /** The name of the intent for the service to check whether an app is re ady to pay. */
45 private static final String ACTION_IS_READY_TO_PAY =
46 "org.chromium.intent.action.IS_READY_TO_PAY";
47
48 /** The name of the intent for the action of paying using "basic-card" m ethod. */
49 private static final String ACTION_PAY_BASIC_CARD =
50 "org.chromium.intent.action.PAY_BASIC_CARD";
51
52 /**
53 * The basic-card payment method name used by merchant and defined by W3C :
gogerald1 2017/01/23 17:18:35 one more space for indentation
please use gerrit instead 2017/02/23 19:57:50 Done.
54 * https://w3c.github.io/webpayments-methods-card/#method-id
55 */
56 private static final String BASIC_CARD_PAYMENT_METHOD = "basic-card";
57
58
59 /** The maximum number of payment method manifests to download. */
60 private static final int MAX_NUMBER_OF_MANIFESTS = 10;
61
62 private final Context mContext;
63 private final WebContents mWebContents;
64 private final PaymentAppCreatedCallback mCallback;
65
66 /**
67 * A map of payment method names to the list of unverified (yet) Android apps that claim to
68 * handle these methods.
69 */
70 private final Map<String, Set<ResolveInfo>> mPendingApps;
71
72 /** A map of Android package name to the payment app. */
73 private final Map<String, AndroidPaymentApp> mResult;
74
75 /**
76 * Builds a native Android payment app finder.
77 *
78 * @param context The application context.
79 * @param webContents The web contents that invoked the web payments API .
80 * @param methods The list of payment methods requested by the merch ant.
81 * @param callback The asynchronous callback to be invoked (on the UI thread) when all
82 * Android payment apps have been found.
83 */
84 public PaymentAppFinder(Context context, WebContents webContents, Set<St ring> methods,
85 PaymentAppCreatedCallback callback) {
86 mContext = context;
87 mWebContents = webContents;
88 mCallback = callback;
89 mPendingApps = new HashMap<>();
90 for (String method : methods) {
91 mPendingApps.put(method, null);
gogerald1 2017/01/23 17:18:35 nit: It looks a little more clear to me if we cons
please use gerrit instead 2017/02/23 19:57:50 Done.
92 }
93 mResult = new HashMap<>();
94 }
95
96 /** Initiates the processing of looking for native Android payment apps. */
97 public void find() {
98 PackageManager pm = mContext.getPackageManager();
99 Intent payIntent = new Intent();
100
101 if (mPendingApps.containsKey(BASIC_CARD_PAYMENT_METHOD)) {
102 payIntent.setAction(ACTION_PAY_BASIC_CARD);
103 List<ResolveInfo> apps = pm.queryIntentActivities(payIntent, 0);
104 if (!apps.isEmpty()) {
gogerald1 2017/01/23 17:18:35 nits: positive logic first?
please use gerrit instead 2017/02/23 19:57:50 Done.
105 mPendingApps.put(BASIC_CARD_PAYMENT_METHOD, new HashSet<>(ap ps));
106 } else {
107 mPendingApps.remove(BASIC_CARD_PAYMENT_METHOD);
108 }
109 }
110
111 payIntent.setAction(AndroidPaymentApp.ACTION_PAY);
112 List<PaymentManifestVerifier> verifiers = new ArrayList<>();
113 for (String methodName : mPendingApps.keySet()) {
114 if (!methodName.startsWith(ManifestDownloader.MANIFEST_SCHEME)) continue;
115
47 payIntent.setData(Uri.parse(methodName)); 116 payIntent.setData(Uri.parse(methodName));
48 } else if (methodName.equals(BASIC_CARD_PAYMENT_METHOD)) { 117 List<ResolveInfo> apps = pm.queryIntentActivities(payIntent, 0);
49 payIntent.setAction(ACTION_PAY_BASIC_CARD); 118 if (apps.isEmpty()) continue;
gogerald1 2017/01/23 17:18:34 record and remove these unsupported methods from m
please use gerrit instead 2017/02/23 19:57:50 I don't see the advantage of this. Please explain.
50 payIntent.setData(null); 119
51 } else { 120 verifiers.add(new PaymentManifestVerifier(methodName, apps));
52 continue; 121 mPendingApps.put(methodName, new HashSet<>(apps));
53 } 122 if (verifiers.size() == MAX_NUMBER_OF_MANIFESTS) break;
54 123 }
55 List<ResolveInfo> matches = pm.queryIntentActivities(payIntent, 0); 124
56 for (int i = 0; i < matches.size(); i++) { 125 Set<ResolveInfo> basicCardPendingApps = mPendingApps.get(BASIC_CARD_ PAYMENT_METHOD);
57 ResolveInfo match = matches.get(i); 126 if (basicCardPendingApps != null) {
58 String packageName = match.activityInfo.packageName; 127 // Iterate over a copy of the set, because onValidPaymentApp alt ers the original.
59 AndroidPaymentApp installedApp = installedApps.get(packageName); 128 Set<ResolveInfo> copySet = new HashSet<>(basicCardPendingApps);
60 if (installedApp == null) { 129 for (ResolveInfo app : copySet) {
61 CharSequence label = match.loadLabel(pm); 130 onValidPaymentApp(BASIC_CARD_PAYMENT_METHOD, app);
62 installedApp = 131 }
63 new AndroidPaymentApp(webContents, packageName, matc h.activityInfo.name, 132 }
64 label == null ? "" : label.toString(), match .loadIcon(pm)); 133
65 callback.onPaymentAppCreated(installedApp); 134 for (int i = 0; i < verifiers.size(); i++) {
66 installedApps.put(packageName, installedApp); 135 verifiers.get(i).verify();
67 } 136 }
68 installedApp.addMethodName(methodName); 137 }
69 } 138
70 } 139 /**
71 140 * Enables invoking the given native Android payment app for the given p ayment method.
72 List<ResolveInfo> matches = pm.queryIntentServices(new Intent(ACTION_IS_ READY_TO_PAY), 0); 141 * Called when the app has been found to have the right privileges to ha ndle this payment
73 for (int i = 0; i < matches.size(); i++) { 142 * method.
74 ResolveInfo match = matches.get(i); 143 *
75 String packageName = match.serviceInfo.packageName; 144 * @param methodName The payment method name that the payment app offer s to handle.
76 AndroidPaymentApp installedApp = installedApps.get(packageName); 145 * @param resolveInfo Identifying information for the native Android pay ment app.
77 if (installedApp != null) installedApp.setIsReadyToPayAction(match.s erviceInfo.name); 146 */
78 } 147 private void onValidPaymentApp(String methodName, ResolveInfo resolveInf o) {
79 148 PackageManager pm = mContext.getPackageManager();
80 callback.onAllPaymentAppsCreated(); 149 String packageName = resolveInfo.activityInfo.packageName;
150 AndroidPaymentApp app = mResult.get(packageName);
151 if (app == null) {
152 CharSequence label = resolveInfo.loadLabel(pm);
153 app = new AndroidPaymentApp(mWebContents, packageName,
154 resolveInfo.activityInfo.name, label == null ? "" : labe l.toString(),
155 resolveInfo.loadIcon(pm));
156 mResult.put(packageName, app);
157 }
158 app.addMethodName(methodName);
159 removePendingApp(methodName, resolveInfo);
160 }
161
162 /**
163 * Disables invoking the given native Android payment app for the given payment method.
164 * Called when the app has been found to not have the right privileges t o handle this
165 * payment app.
166 *
167 * @param methodName The payment method name that the payment app offer s to handle.
168 * @param resolveInfo Identifying information for the native Android pay ment app.
169 */
170 private void onInvalidApp(String methodName, ResolveInfo resolveInfo) {
171 removePendingApp(methodName, resolveInfo);
172 }
173
174 /** Removes the method/app pair from the list of pending information to be verified. */
175 private void removePendingApp(String methodName, ResolveInfo resolveInfo ) {
176 Set<ResolveInfo> pendingAppsForMethod = mPendingApps.get(methodName) ;
177 pendingAppsForMethod.remove(resolveInfo);
178 if (pendingAppsForMethod.isEmpty()) mPendingApps.remove(methodName);
179 if (mPendingApps.isEmpty()) onSearchFinished();
180 }
181
182 /**
183 * Disables invoking any native Android payment app for the given paymen t method. Called if
184 * unable to download or parse the payment method manifest.
185 *
186 * @param methodName The payment method name that has an invalid payment method manifest.
187 */
188 private void onInvalidManifest(String methodName) {
189 mPendingApps.remove(methodName);
190 if (mPendingApps.isEmpty()) onSearchFinished();
191 }
192
193 /**
194 * Checks for IS_READY_TO_PAY service in each valid payment app and retu rns the valid apps
195 * to the caller. Called when finished verifying all payment methods and apps.
196 */
197 private void onSearchFinished() {
198 List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryI ntentServices(
199 new Intent(ACTION_IS_READY_TO_PAY), 0);
gogerald1 2017/01/23 17:18:34 Could we do this when creating AndroidPaymentApp s
please use gerrit instead 2017/02/23 19:57:50 I don't see how that would improve efficiency. Ple
200 for (int i = 0; i < resolveInfos.size(); i++) {
201 ResolveInfo resolveInfo = resolveInfos.get(i);
202 AndroidPaymentApp app = mResult.get(resolveInfo.serviceInfo.pack ageName);
203 if (app != null) {
204 app.setIsReadyToPayAction(resolveInfo.serviceInfo.name);
205 mCallback.onPaymentAppCreated(app);
gogerald1 2017/01/23 17:18:34 Is this means that the app must support ACTION_IS_
please use gerrit instead 2017/02/23 19:57:50 Fixed. It should be optional.
206 }
207 }
208
209 mCallback.onAllPaymentAppsCreated();
210 }
211
212 /**
213 * Verifies that the discovered native Android payment apps have the suf ficient privileges
214 * to handle a single payment method. Downloads and parses the manifest to compare package
215 * names, versions, and signatures to the apps.
216 */
217 private class PaymentManifestVerifier
218 implements ManifestDownloadCallback, ManifestParseCallback {
219 private final String mMethodName;
220 private final List<AppInfo> mMatchingApps;
221
222 /** Identifying information about an installed native Android paymen t app. */
223 private class AppInfo {
224 /** Identifies a native Android payment app. */
225 public ResolveInfo resolveInfo;
226
227 /** The version code for the native Android payment app, e.g., 1 23. */
228 public long version;
229
230 /**
231 * The SHA256 certificate fingerprints for the native Android pa yment app, .e.g,
232 * ["308201dd30820146020101300d06092a864886f70d01010505003037311 630140"]. Order does
233 * not matter for comparison.
234 */
235 public Set<String> sha256CertFingerprints;
236 }
237
238 /**
239 * Builds the manifest verifier.
240 *
241 * @param methodName The name of the payment method name that apps offer to handle.
242 * Must be a valid URL that starts with "https:/ /".
243 * @param matchingApps The identifying information for the native An droid payment apps
244 * that offer to handle this payment method.
245 */
246 public PaymentManifestVerifier(String methodName, List<ResolveInfo> matchingApps) {
247 assert methodName != null;
248 assert matchingApps != null;
249 mMethodName = methodName;
250 mMatchingApps = new ArrayList<>();
251 for (int i = 0; i < matchingApps.size(); i++) {
252 AppInfo appInfo = new AppInfo();
253 appInfo.resolveInfo = matchingApps.get(i);
254 mMatchingApps.add(appInfo);
255 }
256 }
257
258 /**
259 * Begins the process of verifying that the discovered native Androi d payment apps have
260 * the sufficient privileges to handle this payment method.
261 */
262 public void verify() {
263 ManifestDownloader.download(mMethodName, this);
264 }
265
266 @Override
267 public void onManifestDownloadSuccess(String content) {
268 ManifestParser.parse(content, this);
269 }
270
271 @Override
272 public void onManifestDownloadFailure() {
273 onInvalidManifest(mMethodName);
274 }
275
276 @Override
277 public void onManifestParseSuccess(List<Manifest> manifests) {
278 for (int i = 0; i < manifests.size(); i++) {
279 Manifest manifest = manifests.get(i);
280 if ("*".equals(manifest.packageName)) {
gogerald1 2017/01/23 17:18:35 add comments to explain * means all payment apps a
please use gerrit instead 2017/02/23 19:57:50 Done.
281 for (int j = 0; j < mMatchingApps.size(); j++) {
282 onValidPaymentApp(mMethodName, mMatchingApps.get(j). resolveInfo);
283 }
284 return;
285 }
286 }
287
288 PackageManager pm = mContext.getPackageManager();
289 for (int i = 0; i < mMatchingApps.size(); i++) {
290 AppInfo appInfo = mMatchingApps.get(i);
291 try {
292 PackageInfo packageInfo =
293 pm.getPackageInfo(appInfo.resolveInfo.activityIn fo.packageName,
294 PackageManager.GET_SIGNATURES);
295 appInfo.version = packageInfo.versionCode;
296 appInfo.sha256CertFingerprints = new HashSet<>();
297 Signature[] signatures = packageInfo.signatures;
298 for (int j = 0; j < signatures.length; j++) {
299 appInfo.sha256CertFingerprints.add(signatures[j].toC harsString());
300 }
301 } catch (NameNotFoundException e) {
302 // Leaving appInfo.sha256CertFingerprints uninitialized will call
303 // onInvalidApp() for this app below.
304 }
305 }
306
307 for (int i = 0; i < mMatchingApps.size(); i++) {
308 AppInfo appInfo = mMatchingApps.get(i);
309 boolean isAllowed = false;
310 for (int j = 0; j < manifests.size(); j++) {
311 Manifest manifest = manifests.get(j);
312 if (appInfo.resolveInfo.activityInfo.packageName.equals(
313 manifest.packageName)
314 && appInfo.version >= manifest.version
315 && appInfo.sha256CertFingerprints != null
316 && appInfo.sha256CertFingerprints.equals(
317 manifest.sha256CertFingerprints)) {
318 onValidPaymentApp(mMethodName, appInfo.resolveInfo);
319 isAllowed = true;
320 break;
321 }
322 }
323 if (!isAllowed) onInvalidApp(mMethodName, appInfo.resolveInf o);
324 }
325 }
326
327 @Override
328 public void onManifestParseFailure() {
329 onInvalidManifest(mMethodName);
330 }
331 }
81 } 332 }
82 } 333 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698