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(); |
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 { |
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 | |
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; | |
gogerald1
2017/03/03 19:40:27
TextUtils.isEmpty(method) might be better
please use gerrit instead
2017/03/09 18:05:32
Done.
| |
102 if (method.startsWith(UrlConstants.HTTPS_URL_PREFIX)) { | |
103 URI uri = null; | |
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())) { | |
gogerald1
2017/03/03 19:40:27
Is it possible that the scheme is not 'https' afte
please use gerrit instead
2017/03/09 18:05:32
Done.
| |
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); | |
128 if (apps.isEmpty()) continue; | |
129 | |
130 if (mParser == null) { | |
131 mParser = new PaymentManifestParser(); | |
132 mParser.startUtilityProcess(); | |
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; | |
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(); | |
gogerald1
2017/03/03 19:40:27
move inside if (app == null)
please use gerrit instead
2017/03/09 18:05:32
No longer applicable after abstracting away Packag
| |
169 String packageName = resolveInfo.activityInfo.packageName; | |
170 AndroidPaymentApp app = mResult.get(packageName); | |
171 if (app == null) { | |
172 CharSequence label = resolveInfo.loadLabel(pm); | |
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 | |
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 |