OLD | NEW |
---|---|
(Empty) | |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.chrome.browser.payments; | |
6 | |
7 import android.content.Intent; | |
8 import android.content.pm.ResolveInfo; | |
9 import android.net.Uri; | |
10 import android.text.TextUtils; | |
11 | |
12 import org.chromium.base.Log; | |
13 import org.chromium.chrome.browser.UrlConstants; | |
14 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedC allback; | |
15 import org.chromium.chrome.browser.payments.PaymentManifestVerifier.ManifestVeri fyCallback; | |
16 import org.chromium.components.payments.PaymentManifestDownloader; | |
17 import org.chromium.components.payments.PaymentManifestParser; | |
18 import org.chromium.content_public.browser.WebContents; | |
19 | |
20 import java.net.URI; | |
21 import java.net.URISyntaxException; | |
22 import java.util.ArrayList; | |
23 import java.util.HashMap; | |
24 import java.util.HashSet; | |
25 import java.util.List; | |
26 import java.util.Map; | |
27 import java.util.Set; | |
28 | |
29 /** | |
30 * Finds installed native Android payment apps and verifies their signatures acc ording to the | |
31 * payment method manifests. The manifests are located based on the payment meth od name, which | |
32 * is a URI that starts with "https://". The "basic-card" payment method is an e xception: it's a | |
33 * common payment method that does not have a manifest and can be used by any pa yment app. | |
34 */ | |
35 public class AndroidPaymentAppFinder implements ManifestVerifyCallback { | |
36 private static final String TAG = "cr_PaymentAppFinder"; | |
37 | |
38 /** The name of the intent for the service to check whether an app is ready to pay. */ | |
39 private static final String ACTION_IS_READY_TO_PAY = | |
40 "org.chromium.intent.action.IS_READY_TO_PAY"; | |
41 | |
42 /** The name of the intent for the action of paying using "basic-card" metho d. */ | |
43 private static final String ACTION_PAY_BASIC_CARD = "org.chromium.intent.act ion.PAY_BASIC_CARD"; | |
44 | |
45 /** | |
46 * The basic-card payment method name used by merchant and defined by W3C: | |
47 * https://w3c.github.io/webpayments-methods-card/#method-id | |
48 */ | |
49 private static final String BASIC_CARD_PAYMENT_METHOD = "basic-card"; | |
50 | |
51 /** The maximum number of payment method manifests to download. */ | |
52 private static final int MAX_NUMBER_OF_MANIFESTS = 10; | |
53 | |
54 private final WebContents mWebContents; | |
55 private final boolean mQueryBasicCard; | |
56 private final Set<URI> mPaymentMethods; | |
57 private final PaymentManifestDownloader mDownloader; | |
58 private final PaymentManifestParser mParser; | |
59 private final PackageManagerDelegate mPackageManagerDelegate; | |
60 private final PaymentAppCreatedCallback mCallback; | |
61 | |
62 /** | |
63 * A map of payment method names to the list of (yet) unverified Android app s that claim to | |
64 * handle these methods. Example payment method names in this data structure : | |
65 * "https://bobpay.com", "https://android.com/pay". Basic card is excluded. | |
66 */ | |
67 private final Map<URI, Set<ResolveInfo>> mPendingApps; | |
68 | |
69 /** A map of Android package name to the payment app. */ | |
70 private final Map<String, AndroidPaymentApp> mResult; | |
71 | |
72 /** | |
73 * Whether payment apps are required to have an intent filter with a single PAY action and no | |
74 * additional data, i.e., whether payments apps are required to show up in " Autofill and | |
75 * Payments" settings. | |
76 */ | |
77 private final boolean mRequireShowInSettings; | |
78 | |
79 /** | |
80 * The intent filter for a single PAY action and no additional data. Used to filter out payment | |
81 * apps that don't show up in "Autofill and Payments" settings. | |
82 */ | |
83 private final Intent mSettingsLookup; | |
84 | |
85 /** | |
86 * Finds native Android payment apps. | |
87 * | |
88 * @param webContents The web contents that invoked the web payme nts API. | |
89 * @param methods The list of payment methods requested by th e merchant. For | |
90 * example, "https://bobpay.com", "https://and roid.com/pay", | |
91 * "basic-card". | |
92 * @param requireShowInSettings Whether payment apps are required to show u p in "autofill and | |
93 * Payments" settings. | |
94 * @param downloader The manifest downloader. | |
95 * @param parser The manifest parser. | |
96 * @param packageManagerDelegate The package information retriever. | |
97 * @param callback The asynchronous callback to be invoked (on the UI thread) when | |
98 * all Android payment apps have been found. | |
99 */ | |
100 public static void find(WebContents webContents, Set<String> methods, | |
101 boolean requireShowInSettings, PaymentManifestDownloader downloader, | |
102 PaymentManifestParser parser, PackageManagerDelegate packageManagerD elegate, | |
103 PaymentAppCreatedCallback callback) { | |
104 new AndroidPaymentAppFinder(webContents, methods, requireShowInSettings, downloader, parser, | |
105 packageManagerDelegate, callback) | |
106 .findAndroidPaymentApps(); | |
107 } | |
108 | |
109 private AndroidPaymentAppFinder(WebContents webContents, Set<String> methods , | |
110 boolean requireShowInSettings, PaymentManifestDownloader downloader, | |
111 PaymentManifestParser parser, PackageManagerDelegate packageManagerD elegate, | |
112 PaymentAppCreatedCallback callback) { | |
113 mWebContents = webContents; | |
114 mQueryBasicCard = methods.contains(BASIC_CARD_PAYMENT_METHOD); | |
115 mPaymentMethods = new HashSet<>(); | |
116 for (String method : methods) { | |
117 assert !TextUtils.isEmpty(method); | |
118 | |
119 if (!method.startsWith(UrlConstants.HTTPS_URL_PREFIX)) continue; | |
120 | |
121 URI uri; | |
122 try { | |
123 // Don't use java.net.URL, because it performs a synchronous DNS lookup in | |
124 // the constructor. | |
125 uri = new URI(method); | |
126 } catch (URISyntaxException e) { | |
127 continue; | |
128 } | |
129 | |
130 if (uri.isAbsolute()) { | |
131 assert UrlConstants.HTTPS_SCHEME.equals(uri.getScheme()); | |
132 mPaymentMethods.add(uri); | |
133 } | |
134 } | |
135 | |
136 mDownloader = downloader; | |
137 mParser = parser; | |
138 mPackageManagerDelegate = packageManagerDelegate; | |
139 mCallback = callback; | |
140 mPendingApps = new HashMap<>(); | |
141 mResult = new HashMap<>(); | |
142 mRequireShowInSettings = requireShowInSettings; | |
143 mSettingsLookup = new Intent(AndroidPaymentApp.ACTION_PAY); | |
144 } | |
145 | |
146 private void findAndroidPaymentApps() { | |
147 List<PaymentManifestVerifier> verifiers = new ArrayList<>(); | |
148 if (!mPaymentMethods.isEmpty()) { | |
149 Intent payIntent = new Intent(AndroidPaymentApp.ACTION_PAY); | |
150 for (URI methodName : mPaymentMethods) { | |
151 payIntent.setData(Uri.parse(methodName.toString())); | |
152 List<ResolveInfo> apps = | |
153 mPackageManagerDelegate.getActivitiesThatCanRespondToInt ent(payIntent); | |
154 if (apps.isEmpty()) continue; | |
155 | |
156 // Start the parser utility process as soon as possible, once we know that a | |
157 // manifest file needs to be parsed. The startup can take up to 2 seconds. | |
158 if (!mParser.isUtilityProcessRunning()) mParser.startUtilityProc ess(); | |
159 | |
160 verifiers.add(new PaymentManifestVerifier(methodName, apps, mDow nloader, mParser, | |
161 mPackageManagerDelegate, this /* callback */)); | |
162 mPendingApps.put(methodName, new HashSet<>(apps)); | |
163 if (verifiers.size() == MAX_NUMBER_OF_MANIFESTS) { | |
164 Log.d(TAG, "Reached maximum number of allowed payment app ma nifests."); | |
165 break; | |
166 } | |
167 } | |
168 } | |
169 | |
170 if (mQueryBasicCard) { | |
171 Intent basicCardPayIntent = new Intent(ACTION_PAY_BASIC_CARD); | |
172 List<ResolveInfo> apps = | |
173 mPackageManagerDelegate.getActivitiesThatCanRespondToIntent( basicCardPayIntent); | |
174 for (int i = 0; i < apps.size(); i++) { | |
175 // Chrome does not verify app manifests for "basic-card" support . | |
176 onValidPaymentApp(BASIC_CARD_PAYMENT_METHOD, apps.get(i)); | |
177 } | |
178 } | |
179 | |
180 if (verifiers.isEmpty()) { | |
181 onSearchFinished(); | |
182 return; | |
183 } | |
184 | |
185 for (int i = 0; i < verifiers.size(); i++) { | |
186 verifiers.get(i).verify(); | |
187 } | |
188 } | |
189 | |
190 @Override | |
191 public void onValidPaymentApp(URI methodName, ResolveInfo resolveInfo) { | |
192 onValidPaymentApp(methodName.toString(), resolveInfo); | |
193 removePendingApp(methodName, resolveInfo); | |
194 } | |
195 | |
196 /** Same as above, but also works for non-URI method names, e.g., "basic-car d". */ | |
197 private void onValidPaymentApp(String methodName, ResolveInfo resolveInfo) { | |
198 String packageName = resolveInfo.activityInfo.packageName; | |
199 AndroidPaymentApp app = mResult.get(packageName); | |
200 if (app == null) { | |
201 if (mRequireShowInSettings) { | |
202 mSettingsLookup.setPackage(packageName); | |
203 if (mPackageManagerDelegate.resolveActivity(mSettingsLookup) == null) return; | |
204 } | |
205 CharSequence label = mPackageManagerDelegate.getAppLabel(resolveInfo ); | |
206 if (TextUtils.isEmpty(label)) return; | |
Ted C
2017/03/13 21:13:34
I'd add logging here too that we skipped the packa
please use gerrit instead
2017/03/13 22:19:54
Done.
| |
207 app = new AndroidPaymentApp(mWebContents, packageName, resolveInfo.a ctivityInfo.name, | |
208 label.toString(), mPackageManagerDelegate.getAppIcon(resolve Info)); | |
209 mResult.put(packageName, app); | |
210 } | |
211 app.addMethodName(methodName); | |
212 } | |
213 | |
214 @Override | |
215 public void onInvalidPaymentApp(URI methodName, ResolveInfo resolveInfo) { | |
216 removePendingApp(methodName, resolveInfo); | |
217 } | |
218 | |
219 /** Removes the (method, app) pair from the list of pending information to b e verified. */ | |
220 private void removePendingApp(URI methodName, ResolveInfo resolveInfo) { | |
221 Set<ResolveInfo> pendingAppsForMethod = mPendingApps.get(methodName); | |
222 pendingAppsForMethod.remove(resolveInfo); | |
223 if (pendingAppsForMethod.isEmpty()) mPendingApps.remove(methodName); | |
224 if (mPendingApps.isEmpty()) onSearchFinished(); | |
225 } | |
226 | |
227 @Override | |
228 public void onInvalidManifest(URI methodName) { | |
229 mPendingApps.remove(methodName); | |
230 if (mPendingApps.isEmpty()) onSearchFinished(); | |
231 } | |
232 | |
233 /** | |
234 * Checks for IS_READY_TO_PAY service in each valid payment app and returns the valid apps | |
235 * to the caller. Called when finished verifying all payment methods and app s. | |
236 */ | |
237 private void onSearchFinished() { | |
238 assert mPendingApps.isEmpty(); | |
239 | |
240 if (mParser.isUtilityProcessRunning()) mParser.stopUtilityProcess(); | |
241 | |
242 List<ResolveInfo> resolveInfos = mPackageManagerDelegate.getServicesThat CanRespondToIntent( | |
243 new Intent(ACTION_IS_READY_TO_PAY)); | |
244 for (int i = 0; i < resolveInfos.size(); i++) { | |
245 ResolveInfo resolveInfo = resolveInfos.get(i); | |
246 AndroidPaymentApp app = mResult.get(resolveInfo.serviceInfo.packageN ame); | |
247 if (app != null) app.setIsReadyToPayAction(resolveInfo.serviceInfo.n ame); | |
248 } | |
249 | |
250 for (Map.Entry<String, AndroidPaymentApp> entry : mResult.entrySet()) { | |
251 mCallback.onPaymentAppCreated(entry.getValue()); | |
252 } | |
253 | |
254 mCallback.onAllPaymentAppsCreated(); | |
255 } | |
256 } | |
OLD | NEW |