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