| 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.Intent; | 7 import android.content.Intent; |
| 8 import android.content.pm.ActivityInfo; | 8 import android.content.pm.ActivityInfo; |
| 9 import android.content.pm.ResolveInfo; | 9 import android.content.pm.ResolveInfo; |
| 10 import android.content.res.Resources; | 10 import android.content.res.Resources; |
| (...skipping 15 matching lines...) Expand all Loading... |
| 26 import java.util.HashSet; | 26 import java.util.HashSet; |
| 27 import java.util.List; | 27 import java.util.List; |
| 28 import java.util.Locale; | 28 import java.util.Locale; |
| 29 import java.util.Map; | 29 import java.util.Map; |
| 30 import java.util.Set; | 30 import java.util.Set; |
| 31 | 31 |
| 32 import javax.annotation.Nullable; | 32 import javax.annotation.Nullable; |
| 33 | 33 |
| 34 /** | 34 /** |
| 35 * Finds installed native Android payment apps and verifies their signatures acc
ording to the | 35 * Finds installed native Android payment apps and verifies their signatures acc
ording to the |
| 36 * payment method manifests. The manifests are located based on the payment meth
od name, which | 36 * payment method manifests. The manifests are located based on the payment meth
od name, which is a |
| 37 * is a URI that starts with "https://". The "basic-card" payment method is an e
xception: it's a | 37 * URI that starts with "https://". The W3C-published non-URI payment method nam
es are exceptions: |
| 38 * common payment method that does not have a manifest and can be used by any pa
yment app. | 38 * these are common payment method names that do not have a manifest and can be
used by any payment |
| 39 * app. |
| 39 */ | 40 */ |
| 40 public class AndroidPaymentAppFinder implements ManifestVerifyCallback { | 41 public class AndroidPaymentAppFinder implements ManifestVerifyCallback { |
| 41 private static final String TAG = "cr_PaymentAppFinder"; | 42 private static final String TAG = "cr_PaymentAppFinder"; |
| 42 | 43 |
| 44 /** The maximum number of payment method manifests to download. */ |
| 45 private static final int MAX_NUMBER_OF_MANIFESTS = 10; |
| 46 |
| 43 /** The name of the intent for the service to check whether an app is ready
to pay. */ | 47 /** The name of the intent for the service to check whether an app is ready
to pay. */ |
| 44 /* package */ static final String ACTION_IS_READY_TO_PAY = | 48 /* package */ static final String ACTION_IS_READY_TO_PAY = |
| 45 "org.chromium.intent.action.IS_READY_TO_PAY"; | 49 "org.chromium.intent.action.IS_READY_TO_PAY"; |
| 46 | 50 |
| 47 /** | 51 /** |
| 48 * The basic-card payment method name used by merchant and defined by W3C: | 52 * The basic-card payment method name used by merchant and defined by W3C: |
| 49 * https://w3c.github.io/webpayments-methods-card/#method-id | 53 * https://w3c.github.io/webpayments-methods-card/#method-id |
| 50 */ | 54 */ |
| 51 /* package */ static final String BASIC_CARD_PAYMENT_METHOD = "basic-card"; | 55 /* package */ static final String BASIC_CARD_PAYMENT_METHOD = "basic-card"; |
| 52 | 56 |
| 53 /** | 57 /** |
| 54 * Meta data name of an app's supported payment method names. | 58 * Meta data name of an app's supported payment method names. |
| 55 */ | 59 */ |
| 56 static final String META_DATA_NAME_OF_PAYMENT_METHOD_NAMES = | 60 /* package */ static final String META_DATA_NAME_OF_PAYMENT_METHOD_NAMES = |
| 57 "org.chromium.payment_method_names"; | 61 "org.chromium.payment_method_names"; |
| 58 | 62 |
| 59 /** | 63 /** |
| 60 * Meta data name of an app's supported default payment method name. | 64 * Meta data name of an app's supported default payment method name. |
| 61 */ | 65 */ |
| 62 static final String META_DATA_NAME_OF_DEFAULT_PAYMENT_METHOD_NAME = | 66 /* package */ static final String META_DATA_NAME_OF_DEFAULT_PAYMENT_METHOD_N
AME = |
| 63 "org.chromium.default_payment_method_name"; | 67 "org.chromium.default_payment_method_name"; |
| 64 | 68 |
| 65 /** The maximum number of payment method manifests to download. */ | |
| 66 private static final int MAX_NUMBER_OF_MANIFESTS = 10; | |
| 67 | |
| 68 private final WebContents mWebContents; | 69 private final WebContents mWebContents; |
| 69 private final boolean mQueryBasicCard; | 70 private final Set<String> mNonUriPaymentMethods; |
| 70 private final Set<URI> mPaymentMethods; | 71 private final Set<URI> mUriPaymentMethods; |
| 71 private final PaymentManifestDownloader mDownloader; | 72 private final PaymentManifestDownloader mDownloader; |
| 72 private final PaymentManifestParser mParser; | 73 private final PaymentManifestParser mParser; |
| 73 private final PackageManagerDelegate mPackageManagerDelegate; | 74 private final PackageManagerDelegate mPackageManagerDelegate; |
| 74 private final PaymentAppCreatedCallback mCallback; | 75 private final PaymentAppCreatedCallback mCallback; |
| 75 private final boolean mIsIncognito; | 76 private final boolean mIsIncognito; |
| 76 | 77 |
| 77 /** | 78 /** |
| 78 * A map of payment method names to the list of (yet) unverified Android app
s that claim to | 79 * A map of payment method names to the list of (yet) unverified Android app
s that claim to |
| 79 * handle these methods. Example payment method names in this data structure
: | 80 * handle these methods. Example payment method names in this data structure
: |
| 80 * "https://bobpay.com", "https://android.com/pay". Basic card is excluded. | 81 * "https://bobpay.com", "https://android.com/pay". Items in the supportedNo
nUriPaymentMethods |
| 82 * are excluded. |
| 81 */ | 83 */ |
| 82 private final Map<URI, Set<ResolveInfo>> mPendingApps; | 84 private final Map<URI, Set<ResolveInfo>> mPendingApps; |
| 83 | 85 |
| 84 /** A map of Android package name to the payment app. */ | 86 /** A map of Android package name to the payment app. */ |
| 85 private final Map<String, AndroidPaymentApp> mResult; | 87 private final Map<String, AndroidPaymentApp> mResult; |
| 86 | 88 |
| 87 /** | 89 /** |
| 88 * Finds native Android payment apps. | 90 * Finds native Android payment apps. |
| 89 * | 91 * |
| 90 * @param webContents The web contents that invoked the web payme
nts API. | 92 * @param webContents The web contents that invoked the web payme
nts API. |
| (...skipping 11 matching lines...) Expand all Loading... |
| 102 PackageManagerDelegate packageManagerDelegate, PaymentAppCreatedCall
back callback) { | 104 PackageManagerDelegate packageManagerDelegate, PaymentAppCreatedCall
back callback) { |
| 103 new AndroidPaymentAppFinder( | 105 new AndroidPaymentAppFinder( |
| 104 webContents, methods, downloader, parser, packageManagerDelegate
, callback) | 106 webContents, methods, downloader, parser, packageManagerDelegate
, callback) |
| 105 .findAndroidPaymentApps(); | 107 .findAndroidPaymentApps(); |
| 106 } | 108 } |
| 107 | 109 |
| 108 private AndroidPaymentAppFinder(WebContents webContents, Set<String> methods
, | 110 private AndroidPaymentAppFinder(WebContents webContents, Set<String> methods
, |
| 109 PaymentManifestDownloader downloader, PaymentManifestParser parser, | 111 PaymentManifestDownloader downloader, PaymentManifestParser parser, |
| 110 PackageManagerDelegate packageManagerDelegate, PaymentAppCreatedCall
back callback) { | 112 PackageManagerDelegate packageManagerDelegate, PaymentAppCreatedCall
back callback) { |
| 111 mWebContents = webContents; | 113 mWebContents = webContents; |
| 112 mQueryBasicCard = methods.contains(BASIC_CARD_PAYMENT_METHOD); | 114 |
| 113 mPaymentMethods = new HashSet<>(); | 115 // For non-URI payment method names, only names published by W3C should
be supported. |
| 116 Set<String> supportedNonUriPaymentMethods = new HashSet<>(); |
| 117 supportedNonUriPaymentMethods.add(BASIC_CARD_PAYMENT_METHOD); |
| 118 |
| 119 mNonUriPaymentMethods = new HashSet<>(); |
| 120 mUriPaymentMethods = new HashSet<>(); |
| 114 for (String method : methods) { | 121 for (String method : methods) { |
| 115 assert !TextUtils.isEmpty(method); | 122 assert !TextUtils.isEmpty(method); |
| 123 if (supportedNonUriPaymentMethods.contains(method)) { |
| 124 mNonUriPaymentMethods.add(method); |
| 125 } else if (method.startsWith(UrlConstants.HTTPS_URL_PREFIX)) { |
| 126 URI uri; |
| 127 try { |
| 128 // Don't use java.net.URL, because it performs a synchronous
DNS lookup in |
| 129 // the constructor. |
| 130 uri = new URI(method); |
| 131 } catch (URISyntaxException e) { |
| 132 continue; |
| 133 } |
| 116 | 134 |
| 117 if (!method.startsWith(UrlConstants.HTTPS_URL_PREFIX)) continue; | 135 if (uri.isAbsolute()) { |
| 118 | 136 assert UrlConstants.HTTPS_SCHEME.equals(uri.getScheme()); |
| 119 URI uri; | 137 mUriPaymentMethods.add(uri); |
| 120 try { | 138 } |
| 121 // Don't use java.net.URL, because it performs a synchronous DNS
lookup in | |
| 122 // the constructor. | |
| 123 uri = new URI(method); | |
| 124 } catch (URISyntaxException e) { | |
| 125 continue; | |
| 126 } | |
| 127 | |
| 128 if (uri.isAbsolute()) { | |
| 129 assert UrlConstants.HTTPS_SCHEME.equals(uri.getScheme()); | |
| 130 mPaymentMethods.add(uri); | |
| 131 } | 139 } |
| 132 } | 140 } |
| 133 | 141 |
| 134 mDownloader = downloader; | 142 mDownloader = downloader; |
| 135 mParser = parser; | 143 mParser = parser; |
| 136 mPackageManagerDelegate = packageManagerDelegate; | 144 mPackageManagerDelegate = packageManagerDelegate; |
| 137 mCallback = callback; | 145 mCallback = callback; |
| 138 mPendingApps = new HashMap<>(); | 146 mPendingApps = new HashMap<>(); |
| 139 mResult = new HashMap<>(); | 147 mResult = new HashMap<>(); |
| 140 ChromeActivity activity = ChromeActivity.fromWebContents(mWebContents); | 148 ChromeActivity activity = ChromeActivity.fromWebContents(mWebContents); |
| 141 mIsIncognito = activity != null && activity.getCurrentTabModel() != null | 149 mIsIncognito = activity != null && activity.getCurrentTabModel() != null |
| 142 && activity.getCurrentTabModel().isIncognito(); | 150 && activity.getCurrentTabModel().isIncognito(); |
| 143 } | 151 } |
| 144 | 152 |
| 145 private void findAndroidPaymentApps() { | 153 private void findAndroidPaymentApps() { |
| 146 Intent payIntent = new Intent(AndroidPaymentApp.ACTION_PAY); | 154 Intent payIntent = new Intent(AndroidPaymentApp.ACTION_PAY); |
| 147 List<ResolveInfo> apps = | 155 List<ResolveInfo> apps = |
| 148 mPackageManagerDelegate.getActivitiesThatCanRespondToIntentWithM
etaData(payIntent); | 156 mPackageManagerDelegate.getActivitiesThatCanRespondToIntentWithM
etaData(payIntent); |
| 149 if (apps.isEmpty()) { | 157 if (apps.isEmpty()) { |
| 150 onSearchFinished(); | 158 onSearchFinished(); |
| 151 return; | 159 return; |
| 152 } | 160 } |
| 153 | 161 |
| 154 List<String[]> appSupportedMethods = new ArrayList<String[]>(); | 162 List<Set<String>> appSupportedMethods = new ArrayList<>(); |
| 155 for (int i = 0; i < apps.size(); i++) { | 163 for (int i = 0; i < apps.size(); i++) { |
| 156 appSupportedMethods.add(getPaymentMethodNames(apps.get(i).activityIn
fo)); | 164 appSupportedMethods.add(getPaymentMethodNames(apps.get(i).activityIn
fo)); |
| 157 } | 165 } |
| 158 | 166 |
| 159 List<String> appSupportedDefaultMethods = new ArrayList<String>(); | |
| 160 for (int i = 0; i < apps.size(); i++) { | |
| 161 appSupportedDefaultMethods.add(getDefaultPaymentMethodName(apps.get(
i).activityInfo)); | |
| 162 } | |
| 163 | |
| 164 List<PaymentManifestVerifier> verifiers = new ArrayList<>(); | 167 List<PaymentManifestVerifier> verifiers = new ArrayList<>(); |
| 165 for (URI methodName : mPaymentMethods) { | 168 for (URI uriMethodName : mUriPaymentMethods) { |
| 166 List<ResolveInfo> supportedApps = filterAppsByMethodName( | 169 List<ResolveInfo> supportedApps = |
| 167 apps, appSupportedMethods, appSupportedDefaultMethods, metho
dName.toString()); | 170 filterAppsByMethodName(apps, appSupportedMethods, uriMethodN
ame.toString()); |
| 168 if (supportedApps.isEmpty()) continue; | 171 if (supportedApps.isEmpty()) continue; |
| 169 | 172 |
| 170 // Start the parser utility process as soon as possible, once we kno
w that a | 173 // Start the parser utility process as soon as possible, once we kno
w that a |
| 171 // manifest file needs to be parsed. The startup can take up to 2 se
conds. | 174 // manifest file needs to be parsed. The startup can take up to 2 se
conds. |
| 172 if (!mParser.isUtilityProcessRunning()) mParser.startUtilityProcess(
); | 175 if (!mParser.isUtilityProcessRunning()) mParser.startUtilityProcess(
); |
| 173 | 176 |
| 174 verifiers.add(new PaymentManifestVerifier(methodName, supportedApps,
mDownloader, | 177 verifiers.add(new PaymentManifestVerifier(uriMethodName, supportedAp
ps, mDownloader, |
| 175 mParser, mPackageManagerDelegate, this /* callback */)); | 178 mParser, mPackageManagerDelegate, this /* callback */)); |
| 176 mPendingApps.put(methodName, new HashSet<>(supportedApps)); | 179 mPendingApps.put(uriMethodName, new HashSet<>(supportedApps)); |
| 177 | 180 |
| 178 if (verifiers.size() == MAX_NUMBER_OF_MANIFESTS) { | 181 if (verifiers.size() == MAX_NUMBER_OF_MANIFESTS) { |
| 179 Log.d(TAG, "Reached maximum number of allowed payment app manife
sts."); | 182 Log.d(TAG, "Reached maximum number of allowed payment app manife
sts."); |
| 180 break; | 183 break; |
| 181 } | 184 } |
| 182 } | 185 } |
| 183 | 186 |
| 184 if (mQueryBasicCard) { | 187 for (String nonUriMethodName : mNonUriPaymentMethods) { |
| 185 List<ResolveInfo> supportedApps = filterAppsByMethodName(apps, appSu
pportedMethods, | 188 List<ResolveInfo> supportedApps = |
| 186 appSupportedDefaultMethods, BASIC_CARD_PAYMENT_METHOD); | 189 filterAppsByMethodName(apps, appSupportedMethods, nonUriMeth
odName); |
| 187 for (int i = 0; i < supportedApps.size(); i++) { | 190 for (int i = 0; i < supportedApps.size(); i++) { |
| 188 // Chrome does not verify app manifests for "basic-card" support
. | 191 // Chrome does not verify app manifests for non-URI payment meth
od support. |
| 189 onValidPaymentApp(BASIC_CARD_PAYMENT_METHOD, supportedApps.get(i
)); | 192 onValidPaymentApp(nonUriMethodName, supportedApps.get(i)); |
| 190 } | 193 } |
| 191 } | 194 } |
| 192 | 195 |
| 193 if (verifiers.isEmpty()) { | 196 if (verifiers.isEmpty()) { |
| 194 onSearchFinished(); | 197 onSearchFinished(); |
| 195 return; | 198 return; |
| 196 } | 199 } |
| 197 | 200 |
| 198 for (int i = 0; i < verifiers.size(); i++) { | 201 for (int i = 0; i < verifiers.size(); i++) { |
| 199 verifiers.get(i).verify(); | 202 verifiers.get(i).verify(); |
| 200 } | 203 } |
| 201 } | 204 } |
| 202 | 205 |
| 203 @Nullable | 206 @Nullable |
| 204 private String[] getPaymentMethodNames(ActivityInfo activityInfo) { | 207 private Set<String> getPaymentMethodNames(ActivityInfo activityInfo) { |
| 205 if (activityInfo.metaData == null) return null; | 208 Set<String> result = new HashSet<>(); |
| 209 if (activityInfo.metaData == null) return result; |
| 210 |
| 211 String defaultMethodName = |
| 212 activityInfo.metaData.getString(META_DATA_NAME_OF_DEFAULT_PAYMEN
T_METHOD_NAME); |
| 213 if (!TextUtils.isEmpty(defaultMethodName)) result.add(defaultMethodName)
; |
| 206 | 214 |
| 207 int resId = activityInfo.metaData.getInt(META_DATA_NAME_OF_PAYMENT_METHO
D_NAMES); | 215 int resId = activityInfo.metaData.getInt(META_DATA_NAME_OF_PAYMENT_METHO
D_NAMES); |
| 208 if (resId == 0) return null; | 216 if (resId == 0) return result; |
| 209 | 217 |
| 210 Resources resources = | 218 Resources resources = |
| 211 mPackageManagerDelegate.getResourcesForApplication(activityInfo.
applicationInfo); | 219 mPackageManagerDelegate.getResourcesForApplication(activityInfo.
applicationInfo); |
| 212 if (resources == null) return null; | 220 if (resources == null) return result; |
| 213 return resources.getStringArray(resId); | 221 |
| 222 String[] methodNames = resources.getStringArray(resId); |
| 223 if (methodNames == null) return result; |
| 224 |
| 225 for (int i = 0; i < methodNames.length; i++) { |
| 226 result.add(methodNames[i]); |
| 227 } |
| 228 |
| 229 return result; |
| 214 } | 230 } |
| 215 | 231 |
| 216 @Nullable | 232 private static List<ResolveInfo> filterAppsByMethodName( |
| 217 private String getDefaultPaymentMethodName(ActivityInfo activityInfo) { | 233 List<ResolveInfo> apps, List<Set<String>> methodNames, String target
MethodName) { |
| 218 if (activityInfo.metaData == null) return null; | 234 assert apps.size() == methodNames.size(); |
| 219 | 235 |
| 220 return activityInfo.metaData.getString(META_DATA_NAME_OF_DEFAULT_PAYMENT
_METHOD_NAME); | 236 // Note that apps and methodNames must have the same size. The informat
ion at the same |
| 221 } | 237 // index must correspond to the same app. |
| 222 | 238 List<ResolveInfo> supportedApps = new ArrayList<>(); |
| 223 private static List<ResolveInfo> filterAppsByMethodName(List<ResolveInfo> ap
ps, | |
| 224 List<String[]> appsMethods, List<String> appsDefaultMethods, String
targetMethodName) { | |
| 225 assert apps.size() == appsMethods.size(); | |
| 226 assert apps.size() == appsDefaultMethods.size(); | |
| 227 | |
| 228 // Note that apps, appsMethods and appsDefaultMethods must have the same
size. And the | |
| 229 // information at the same index must correspond to the same app. | |
| 230 List<ResolveInfo> supportedApps = new ArrayList<ResolveInfo>(); | |
| 231 for (int i = 0; i < apps.size(); i++) { | 239 for (int i = 0; i < apps.size(); i++) { |
| 232 if (targetMethodName.equals(appsDefaultMethods.get(i))) { | 240 if (methodNames.get(i).contains(targetMethodName)) { |
| 233 supportedApps.add(apps.get(i)); | 241 supportedApps.add(apps.get(i)); |
| 234 continue; | 242 continue; |
| 235 } | 243 } |
| 236 | |
| 237 String[] methods = appsMethods.get(i); | |
| 238 if (methods == null) continue; | |
| 239 for (int j = 0; j < methods.length; j++) { | |
| 240 if (targetMethodName.equals(methods[j])) { | |
| 241 supportedApps.add(apps.get(i)); | |
| 242 break; | |
| 243 } | |
| 244 } | |
| 245 } | 244 } |
| 246 return supportedApps; | 245 return supportedApps; |
| 247 } | 246 } |
| 248 | 247 |
| 249 @Override | 248 @Override |
| 250 public void onValidPaymentApp(URI methodName, ResolveInfo resolveInfo) { | 249 public void onValidPaymentApp(URI methodName, ResolveInfo resolveInfo) { |
| 251 onValidPaymentApp(methodName.toString(), resolveInfo); | 250 onValidPaymentApp(methodName.toString(), resolveInfo); |
| 252 removePendingApp(methodName, resolveInfo); | 251 removePendingApp(methodName, resolveInfo); |
| 253 } | 252 } |
| 254 | 253 |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 311 } | 310 } |
| 312 } | 311 } |
| 313 | 312 |
| 314 for (Map.Entry<String, AndroidPaymentApp> entry : mResult.entrySet()) { | 313 for (Map.Entry<String, AndroidPaymentApp> entry : mResult.entrySet()) { |
| 315 mCallback.onPaymentAppCreated(entry.getValue()); | 314 mCallback.onPaymentAppCreated(entry.getValue()); |
| 316 } | 315 } |
| 317 | 316 |
| 318 mCallback.onAllPaymentAppsCreated(); | 317 mCallback.onAllPaymentAppsCreated(); |
| 319 } | 318 } |
| 320 } | 319 } |
| OLD | NEW |