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; | 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 } |
OLD | NEW |