Chromium Code Reviews| 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 |