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; | 15 import org.chromium.base.annotations.SuppressFBWarnings; |
17 import org.chromium.chrome.browser.UrlConstants; | 16 import org.chromium.chrome.browser.UrlConstants; |
18 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedC allback; | 17 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedC allback; |
19 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppFactoryA ddition; | 18 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppFactoryA ddition; |
19 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentManifestPar ser; | |
20 import org.chromium.chrome.browser.payments.PaymentManifestVerifier.ManifestVeri fyCallback; | |
20 import org.chromium.content_public.browser.WebContents; | 21 import org.chromium.content_public.browser.WebContents; |
21 | 22 |
23 import java.net.MalformedURLException; | |
24 import java.net.URL; | |
25 import java.util.ArrayList; | |
22 import java.util.HashMap; | 26 import java.util.HashMap; |
27 import java.util.HashSet; | |
23 import java.util.List; | 28 import java.util.List; |
24 import java.util.Map; | 29 import java.util.Map; |
25 import java.util.Set; | 30 import java.util.Set; |
26 | 31 |
27 /** Builds instances of payment apps based on installed third party Android paym ent apps. */ | 32 /** Builds instances of payment apps based on installed third party Android paym ent apps. */ |
28 public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition { | 33 public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition { |
29 private static final String ACTION_IS_READY_TO_PAY = | 34 @Override |
30 "org.chromium.intent.action.IS_READY_TO_PAY"; | 35 public void create(WebContents webContents, Set<String> methods, PaymentMani festParser parser, |
31 | 36 PaymentAppCreatedCallback callback) { |
32 /** The action name for the Pay Basic-card Intent. */ | 37 new PaymentAppFinder( |
33 private static final String ACTION_PAY_BASIC_CARD = "org.chromium.intent.act ion.PAY_BASIC_CARD"; | 38 webContents, methods, new PaymentManifestDownloader(webContents) , parser, callback) |
39 .find(); | |
40 } | |
34 | 41 |
35 /** | 42 /** |
36 * The basic-card payment method name used by merchant and defined by W3C: | 43 * Finds installed native Android payment apps and verifies their signatures according to the |
37 * https://w3c.github.io/webpayments-methods-card/#method-id | 44 * payment method manifests. The manifests are located based on the payment method name, which |
45 * is a URL that starts with "https://". The "basic-card" payment method is an exception: it's a | |
46 * common payment method that does not have a manifest and can be used by an y payment app. | |
38 */ | 47 */ |
39 private static final String BASIC_CARD_PAYMENT_METHOD = "basic-card"; | 48 @SuppressFBWarnings(value = {"DMI_COLLECTION_OF_URLS"}, justification = "At most 10 elements") |
40 | 49 private static class PaymentAppFinder implements ManifestVerifyCallback { |
41 @Override | 50 /** The name of the intent for the service to check whether an app is re ady to pay. */ |
42 public void create(WebContents webContents, Set<String> methods, | 51 private static final String ACTION_IS_READY_TO_PAY = |
43 PaymentAppCreatedCallback callback) { | 52 "org.chromium.intent.action.IS_READY_TO_PAY"; |
44 Context context = ChromeActivity.fromWebContents(webContents); | 53 |
45 if (context == null) { | 54 /** The name of the intent for the action of paying using "basic-card" m ethod. */ |
46 callback.onAllPaymentAppsCreated(); | 55 private static final String ACTION_PAY_BASIC_CARD = |
47 return; | 56 "org.chromium.intent.action.PAY_BASIC_CARD"; |
48 } | 57 |
49 | 58 /** |
50 Map<String, AndroidPaymentApp> installedApps = new HashMap<>(); | 59 * The basic-card payment method name used by merchant and defined by W3 C: |
51 PackageManager pm = context.getPackageManager(); | 60 * https://w3c.github.io/webpayments-methods-card/#method-id |
52 Intent payIntent = new Intent(); | 61 */ |
53 | 62 private static final String BASIC_CARD_PAYMENT_METHOD = "basic-card"; |
54 for (String methodName : methods) { | 63 |
55 if (methodName.startsWith(UrlConstants.HTTPS_URL_PREFIX)) { | 64 /** The maximum number of payment method manifests to download. */ |
56 payIntent.setAction(AndroidPaymentApp.ACTION_PAY); | 65 private static final int MAX_NUMBER_OF_MANIFESTS = 10; |
57 payIntent.setData(Uri.parse(methodName)); | 66 |
58 } else if (methodName.equals(BASIC_CARD_PAYMENT_METHOD)) { | 67 private final WebContents mWebContents; |
68 private final PaymentManifestDownloader mDownloader; | |
69 private final PaymentManifestParser mParser; | |
70 private final PaymentAppCreatedCallback mCallback; | |
71 | |
72 /** Whether "basic-card" supporting payment apps should be queried. */ | |
73 private final boolean mQueryBasicCard; | |
74 | |
75 /** | |
76 * A map of payment method names to the list of unverified (yet) Android apps that claim to | |
77 * handle these methods. Example payment method names in this data struc ture: | |
78 * "https://bobpay.com", "https://android.com/pay". Basic card is exclud ed. | |
79 */ | |
80 private final Map<URL, Set<ResolveInfo>> mPendingApps; | |
81 | |
82 /** A map of Android package name to the payment app. */ | |
83 private final Map<String, AndroidPaymentApp> mResult; | |
84 | |
85 private final Set<URL> mPaymentMethods; | |
86 | |
87 /** | |
88 * Builds a native Android payment app finder. | |
89 * | |
90 * @param webContents The web contents that invoked the web payments API . | |
91 * @param methods The list of payment methods requested by the merch ant. For example, | |
92 * "https://bobpay.com", "https://android.com/pay", " basic-card". | |
93 * @param downloader The payment manifest downloader. | |
94 * @param parser The payment manifest parser. | |
95 * @param callback The asynchronous callback to be invoked (on the UI thread) when all | |
96 * Android payment apps have been found. | |
97 */ | |
98 public PaymentAppFinder(WebContents webContents, Set<String> methods, | |
99 PaymentManifestDownloader downloader, PaymentManifestParser pars er, | |
100 PaymentAppCreatedCallback callback) { | |
101 mWebContents = webContents; | |
102 mDownloader = downloader; | |
103 mParser = parser; | |
104 mCallback = callback; | |
105 mQueryBasicCard = methods.contains(BASIC_CARD_PAYMENT_METHOD); | |
106 mPendingApps = new HashMap<>(); | |
107 mResult = new HashMap<>(); | |
108 | |
109 mPaymentMethods = new HashSet<>(); | |
110 for (String method : methods) { | |
111 assert method != null; | |
112 if (method.startsWith(UrlConstants.HTTPS_URL_PREFIX)) { | |
113 URL url = null; | |
114 try { | |
115 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.
| |
116 } catch (MalformedURLException e) { | |
117 continue; | |
118 } | |
119 assert UrlConstants.HTTPS_SCHEME.equals(url.getProtocol()); | |
120 mPaymentMethods.add(url); | |
121 } | |
122 } | |
123 } | |
124 | |
125 /** Looks for native Android payment apps. */ | |
126 public void find() { | |
127 PackageManager pm = ContextUtils.getApplicationContext().getPackageM anager(); | |
128 Intent payIntent = new Intent(); | |
129 payIntent.setAction(AndroidPaymentApp.ACTION_PAY); | |
130 | |
131 List<PaymentManifestVerifier> verifiers = new ArrayList<>(); | |
132 for (URL methodName : mPaymentMethods) { | |
133 payIntent.setData(Uri.parse(methodName.toString())); | |
134 List<ResolveInfo> apps = pm.queryIntentActivities(payIntent, 0); | |
135 if (apps.isEmpty()) continue; | |
136 verifiers.add(new PaymentManifestVerifier( | |
137 pm, methodName, apps, mDownloader, mParser, this)); | |
138 mPendingApps.put(methodName, new HashSet<>(apps)); | |
139 if (verifiers.size() == MAX_NUMBER_OF_MANIFESTS) break; | |
140 } | |
141 | |
142 if (mQueryBasicCard) { | |
59 payIntent.setAction(ACTION_PAY_BASIC_CARD); | 143 payIntent.setAction(ACTION_PAY_BASIC_CARD); |
60 payIntent.setData(null); | 144 payIntent.setData(null); |
61 } else { | 145 List<ResolveInfo> apps = pm.queryIntentActivities(payIntent, 0); |
62 continue; | 146 for (int i = 0; i < apps.size(); i++) { |
63 } | 147 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 } | 148 } |
80 installedApp.addMethodName(methodName); | 149 } |
81 } | 150 |
82 } | 151 if (verifiers.isEmpty()) { |
83 | 152 onSearchFinished(); |
84 List<ResolveInfo> matches = pm.queryIntentServices(new Intent(ACTION_IS_ READY_TO_PAY), 0); | 153 return; |
85 for (int i = 0; i < matches.size(); i++) { | 154 } |
86 ResolveInfo match = matches.get(i); | 155 |
87 String packageName = match.serviceInfo.packageName; | 156 for (int i = 0; i < verifiers.size(); i++) { |
88 AndroidPaymentApp installedApp = installedApps.get(packageName); | 157 verifiers.get(i).verify(); |
89 if (installedApp != null) installedApp.setIsReadyToPayAction(match.s erviceInfo.name); | 158 } |
90 } | 159 } |
91 | 160 |
92 callback.onAllPaymentAppsCreated(); | 161 @Override |
162 public void onValidPaymentApp(URL methodName, ResolveInfo resolveInfo) { | |
163 onValidPaymentApp(methodName.toString(), resolveInfo); | |
164 removePendingApp(methodName, resolveInfo); | |
165 } | |
166 | |
167 /** Same as above, but also works for non-URL method names, e.g., "basic -card". */ | |
168 private void onValidPaymentApp(String methodName, ResolveInfo resolveInf o) { | |
169 PackageManager pm = ContextUtils.getApplicationContext().getPackageM anager(); | |
170 String packageName = resolveInfo.activityInfo.packageName; | |
171 AndroidPaymentApp app = mResult.get(packageName); | |
172 if (app == null) { | |
173 CharSequence label = resolveInfo.loadLabel(pm); | |
174 app = new AndroidPaymentApp(mWebContents, packageName, | |
175 resolveInfo.activityInfo.name, label == null ? "" : labe l.toString(), | |
176 resolveInfo.loadIcon(pm)); | |
177 mResult.put(packageName, app); | |
178 } | |
179 app.addMethodName(methodName); | |
180 } | |
181 | |
182 @Override | |
183 public void onInvalidPaymentApp(URL methodName, ResolveInfo resolveInfo) { | |
184 removePendingApp(methodName, resolveInfo); | |
185 } | |
186 | |
187 /** Removes the (method, app) pair from the list of pending information to be verified. */ | |
188 private void removePendingApp(URL methodName, ResolveInfo resolveInfo) { | |
189 Set<ResolveInfo> pendingAppsForMethod = mPendingApps.get(methodName) ; | |
190 pendingAppsForMethod.remove(resolveInfo); | |
191 if (pendingAppsForMethod.isEmpty()) mPendingApps.remove(methodName); | |
192 if (mPendingApps.isEmpty()) onSearchFinished(); | |
193 } | |
194 | |
195 @Override | |
196 public void onInvalidManifest(URL methodName) { | |
197 mPendingApps.remove(methodName); | |
198 if (mPendingApps.isEmpty()) onSearchFinished(); | |
199 } | |
200 | |
201 /** | |
202 * Checks for IS_READY_TO_PAY service in each valid payment app and retu rns the valid apps | |
203 * to the caller. Called when finished verifying all payment methods and apps. | |
204 */ | |
205 private void onSearchFinished() { | |
206 PackageManager pm = ContextUtils.getApplicationContext().getPackageM anager(); | |
207 List<ResolveInfo> resolveInfos = | |
208 pm.queryIntentServices(new Intent(ACTION_IS_READY_TO_PAY), 0 ); | |
209 for (int i = 0; i < resolveInfos.size(); i++) { | |
210 ResolveInfo resolveInfo = resolveInfos.get(i); | |
211 AndroidPaymentApp app = mResult.get(resolveInfo.serviceInfo.pack ageName); | |
212 if (app != null) app.setIsReadyToPayAction(resolveInfo.serviceIn fo.name); | |
213 } | |
214 | |
215 for (Map.Entry<String, AndroidPaymentApp> entry : mResult.entrySet() ) { | |
216 mCallback.onPaymentAppCreated(entry.getValue()); | |
217 } | |
218 | |
219 mCallback.onAllPaymentAppsCreated(); | |
220 } | |
93 } | 221 } |
94 | 222 |
95 /** | 223 /** |
96 * Checks whether there are Android payment apps on device. | 224 * Checks whether there are Android payment apps on device. |
97 * | 225 * |
98 * @return True if there are Android payment apps on device. | 226 * @return True if there are Android payment apps on device. |
99 */ | 227 */ |
100 public static boolean hasAndroidPaymentApps() { | 228 public static boolean hasAndroidPaymentApps() { |
101 PackageManager pm = ContextUtils.getApplicationContext().getPackageManag er(); | 229 PackageManager pm = ContextUtils.getApplicationContext().getPackageManag er(); |
102 // Note that all Android payment apps must support org.chromium.intent.a ction.PAY action | 230 // Note that all Android payment apps must support org.chromium.intent.a ction.PAY action |
(...skipping 18 matching lines...) Expand all Loading... | |
121 | 249 |
122 for (ResolveInfo match : matches) { | 250 for (ResolveInfo match : matches) { |
123 Pair<String, Drawable> appInfo = | 251 Pair<String, Drawable> appInfo = |
124 new Pair<>(match.loadLabel(pm).toString(), match.loadIcon(pm )); | 252 new Pair<>(match.loadLabel(pm).toString(), match.loadIcon(pm )); |
125 paymentAppsInfo.put(match.activityInfo.packageName, appInfo); | 253 paymentAppsInfo.put(match.activityInfo.packageName, appInfo); |
126 } | 254 } |
127 | 255 |
128 return paymentAppsInfo; | 256 return paymentAppsInfo; |
129 } | 257 } |
130 } | 258 } |
OLD | NEW |