Chromium Code Reviews| 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; | 7 import android.content.Context; |
| 8 import android.content.Intent; | 8 import android.content.Intent; |
| 9 import android.content.pm.PackageInfo; | |
| 9 import android.content.pm.PackageManager; | 10 import android.content.pm.PackageManager; |
| 11 import android.content.pm.PackageManager.NameNotFoundException; | |
| 10 import android.content.pm.ResolveInfo; | 12 import android.content.pm.ResolveInfo; |
| 13 import android.content.pm.Signature; | |
| 11 import android.net.Uri; | 14 import android.net.Uri; |
| 12 | 15 |
| 16 import org.chromium.chrome.browser.payments.ManifestDownloader.ManifestDownloadC allback; | |
| 17 import org.chromium.chrome.browser.payments.ManifestParser.Manifest; | |
| 18 import org.chromium.chrome.browser.payments.ManifestParser.ManifestParseCallback ; | |
| 13 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedC allback; | 19 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppCreatedC allback; |
| 14 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppFactoryA ddition; | 20 import org.chromium.chrome.browser.payments.PaymentAppFactory.PaymentAppFactoryA ddition; |
| 15 import org.chromium.content_public.browser.WebContents; | 21 import org.chromium.content_public.browser.WebContents; |
| 16 | 22 |
| 23 import java.util.ArrayList; | |
| 17 import java.util.HashMap; | 24 import java.util.HashMap; |
| 25 import java.util.HashSet; | |
| 18 import java.util.List; | 26 import java.util.List; |
| 19 import java.util.Map; | 27 import java.util.Map; |
| 20 import java.util.Set; | 28 import java.util.Set; |
| 21 | 29 |
| 22 /** Builds instances of payment apps based on installed third party Android paym ent apps. */ | 30 /** Builds instances of payment apps based on installed third party Android paym ent apps. */ |
| 23 public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition { | 31 public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition { |
| 24 private static final String ACTION_IS_READY_TO_PAY = | |
| 25 "org.chromium.intent.action.IS_READY_TO_PAY"; | |
| 26 private static final String METHOD_PREFIX = "https://"; | |
| 27 | |
| 28 /** The action name for the Pay Basic-card Intent. */ | |
| 29 private static final String ACTION_PAY_BASIC_CARD = "org.chromium.intent.act ion.PAY_BASIC_CARD"; | |
| 30 | |
| 31 /** | |
| 32 * The basic-card payment method name used by merchant and defined by W3C: | |
| 33 * https://w3c.github.io/webpayments-methods-card/#method-id | |
| 34 */ | |
| 35 private static final String BASIC_CARD_PAYMENT_METHOD = "basic-card"; | |
| 36 | |
| 37 @Override | 32 @Override |
| 38 public void create(Context context, WebContents webContents, Set<String> met hods, | 33 public void create(Context context, WebContents webContents, Set<String> met hods, |
| 39 PaymentAppCreatedCallback callback) { | 34 PaymentAppCreatedCallback callback) { |
| 40 Map<String, AndroidPaymentApp> installedApps = new HashMap<>(); | 35 new PaymentAppFinder(context, webContents, methods, callback).find(); |
| 41 PackageManager pm = context.getPackageManager(); | 36 } |
| 42 Intent payIntent = new Intent(); | 37 |
| 43 | 38 /** |
| 44 for (String methodName : methods) { | 39 * Finds installed native Android payment apps and verifies their signatures according to the |
| 45 if (methodName.startsWith(METHOD_PREFIX)) { | 40 * payment method manifests. The "basic-card" payment method is an exception : it's a common |
| 46 payIntent.setAction(AndroidPaymentApp.ACTION_PAY); | 41 * payment method that can be used by any payment app. |
| 42 */ | |
| 43 private static class PaymentAppFinder { | |
| 44 /** The name of the intent for the service to check whether an app is re ady to pay. */ | |
| 45 private static final String ACTION_IS_READY_TO_PAY = | |
| 46 "org.chromium.intent.action.IS_READY_TO_PAY"; | |
| 47 | |
| 48 /** The name of the intent for the action of paying using "basic-card" m ethod. */ | |
| 49 private static final String ACTION_PAY_BASIC_CARD = | |
| 50 "org.chromium.intent.action.PAY_BASIC_CARD"; | |
| 51 | |
| 52 /** | |
| 53 * The basic-card payment method name used by merchant and defined by W3C : | |
|
gogerald1
2017/01/23 17:18:35
one more space for indentation
please use gerrit instead
2017/02/23 19:57:50
Done.
| |
| 54 * https://w3c.github.io/webpayments-methods-card/#method-id | |
| 55 */ | |
| 56 private static final String BASIC_CARD_PAYMENT_METHOD = "basic-card"; | |
| 57 | |
| 58 | |
| 59 /** The maximum number of payment method manifests to download. */ | |
| 60 private static final int MAX_NUMBER_OF_MANIFESTS = 10; | |
| 61 | |
| 62 private final Context mContext; | |
| 63 private final WebContents mWebContents; | |
| 64 private final PaymentAppCreatedCallback mCallback; | |
| 65 | |
| 66 /** | |
| 67 * A map of payment method names to the list of unverified (yet) Android apps that claim to | |
| 68 * handle these methods. | |
| 69 */ | |
| 70 private final Map<String, Set<ResolveInfo>> mPendingApps; | |
| 71 | |
| 72 /** A map of Android package name to the payment app. */ | |
| 73 private final Map<String, AndroidPaymentApp> mResult; | |
| 74 | |
| 75 /** | |
| 76 * Builds a native Android payment app finder. | |
| 77 * | |
| 78 * @param context The application context. | |
| 79 * @param webContents The web contents that invoked the web payments API . | |
| 80 * @param methods The list of payment methods requested by the merch ant. | |
| 81 * @param callback The asynchronous callback to be invoked (on the UI thread) when all | |
| 82 * Android payment apps have been found. | |
| 83 */ | |
| 84 public PaymentAppFinder(Context context, WebContents webContents, Set<St ring> methods, | |
| 85 PaymentAppCreatedCallback callback) { | |
| 86 mContext = context; | |
| 87 mWebContents = webContents; | |
| 88 mCallback = callback; | |
| 89 mPendingApps = new HashMap<>(); | |
| 90 for (String method : methods) { | |
| 91 mPendingApps.put(method, null); | |
|
gogerald1
2017/01/23 17:18:35
nit: It looks a little more clear to me if we cons
please use gerrit instead
2017/02/23 19:57:50
Done.
| |
| 92 } | |
| 93 mResult = new HashMap<>(); | |
| 94 } | |
| 95 | |
| 96 /** Initiates the processing of looking for native Android payment apps. */ | |
| 97 public void find() { | |
| 98 PackageManager pm = mContext.getPackageManager(); | |
| 99 Intent payIntent = new Intent(); | |
| 100 | |
| 101 if (mPendingApps.containsKey(BASIC_CARD_PAYMENT_METHOD)) { | |
| 102 payIntent.setAction(ACTION_PAY_BASIC_CARD); | |
| 103 List<ResolveInfo> apps = pm.queryIntentActivities(payIntent, 0); | |
| 104 if (!apps.isEmpty()) { | |
|
gogerald1
2017/01/23 17:18:35
nits: positive logic first?
please use gerrit instead
2017/02/23 19:57:50
Done.
| |
| 105 mPendingApps.put(BASIC_CARD_PAYMENT_METHOD, new HashSet<>(ap ps)); | |
| 106 } else { | |
| 107 mPendingApps.remove(BASIC_CARD_PAYMENT_METHOD); | |
| 108 } | |
| 109 } | |
| 110 | |
| 111 payIntent.setAction(AndroidPaymentApp.ACTION_PAY); | |
| 112 List<PaymentManifestVerifier> verifiers = new ArrayList<>(); | |
| 113 for (String methodName : mPendingApps.keySet()) { | |
| 114 if (!methodName.startsWith(ManifestDownloader.MANIFEST_SCHEME)) continue; | |
| 115 | |
| 47 payIntent.setData(Uri.parse(methodName)); | 116 payIntent.setData(Uri.parse(methodName)); |
| 48 } else if (methodName.equals(BASIC_CARD_PAYMENT_METHOD)) { | 117 List<ResolveInfo> apps = pm.queryIntentActivities(payIntent, 0); |
| 49 payIntent.setAction(ACTION_PAY_BASIC_CARD); | 118 if (apps.isEmpty()) continue; |
|
gogerald1
2017/01/23 17:18:34
record and remove these unsupported methods from m
please use gerrit instead
2017/02/23 19:57:50
I don't see the advantage of this. Please explain.
| |
| 50 payIntent.setData(null); | 119 |
| 51 } else { | 120 verifiers.add(new PaymentManifestVerifier(methodName, apps)); |
| 52 continue; | 121 mPendingApps.put(methodName, new HashSet<>(apps)); |
| 53 } | 122 if (verifiers.size() == MAX_NUMBER_OF_MANIFESTS) break; |
| 54 | 123 } |
| 55 List<ResolveInfo> matches = pm.queryIntentActivities(payIntent, 0); | 124 |
| 56 for (int i = 0; i < matches.size(); i++) { | 125 Set<ResolveInfo> basicCardPendingApps = mPendingApps.get(BASIC_CARD_ PAYMENT_METHOD); |
| 57 ResolveInfo match = matches.get(i); | 126 if (basicCardPendingApps != null) { |
| 58 String packageName = match.activityInfo.packageName; | 127 // Iterate over a copy of the set, because onValidPaymentApp alt ers the original. |
| 59 AndroidPaymentApp installedApp = installedApps.get(packageName); | 128 Set<ResolveInfo> copySet = new HashSet<>(basicCardPendingApps); |
| 60 if (installedApp == null) { | 129 for (ResolveInfo app : copySet) { |
| 61 CharSequence label = match.loadLabel(pm); | 130 onValidPaymentApp(BASIC_CARD_PAYMENT_METHOD, app); |
| 62 installedApp = | 131 } |
| 63 new AndroidPaymentApp(webContents, packageName, matc h.activityInfo.name, | 132 } |
| 64 label == null ? "" : label.toString(), match .loadIcon(pm)); | 133 |
| 65 callback.onPaymentAppCreated(installedApp); | 134 for (int i = 0; i < verifiers.size(); i++) { |
| 66 installedApps.put(packageName, installedApp); | 135 verifiers.get(i).verify(); |
| 67 } | 136 } |
| 68 installedApp.addMethodName(methodName); | 137 } |
| 69 } | 138 |
| 70 } | 139 /** |
| 71 | 140 * Enables invoking the given native Android payment app for the given p ayment method. |
| 72 List<ResolveInfo> matches = pm.queryIntentServices(new Intent(ACTION_IS_ READY_TO_PAY), 0); | 141 * Called when the app has been found to have the right privileges to ha ndle this payment |
| 73 for (int i = 0; i < matches.size(); i++) { | 142 * method. |
| 74 ResolveInfo match = matches.get(i); | 143 * |
| 75 String packageName = match.serviceInfo.packageName; | 144 * @param methodName The payment method name that the payment app offer s to handle. |
| 76 AndroidPaymentApp installedApp = installedApps.get(packageName); | 145 * @param resolveInfo Identifying information for the native Android pay ment app. |
| 77 if (installedApp != null) installedApp.setIsReadyToPayAction(match.s erviceInfo.name); | 146 */ |
| 78 } | 147 private void onValidPaymentApp(String methodName, ResolveInfo resolveInf o) { |
| 79 | 148 PackageManager pm = mContext.getPackageManager(); |
| 80 callback.onAllPaymentAppsCreated(); | 149 String packageName = resolveInfo.activityInfo.packageName; |
| 150 AndroidPaymentApp app = mResult.get(packageName); | |
| 151 if (app == null) { | |
| 152 CharSequence label = resolveInfo.loadLabel(pm); | |
| 153 app = new AndroidPaymentApp(mWebContents, packageName, | |
| 154 resolveInfo.activityInfo.name, label == null ? "" : labe l.toString(), | |
| 155 resolveInfo.loadIcon(pm)); | |
| 156 mResult.put(packageName, app); | |
| 157 } | |
| 158 app.addMethodName(methodName); | |
| 159 removePendingApp(methodName, resolveInfo); | |
| 160 } | |
| 161 | |
| 162 /** | |
| 163 * Disables invoking the given native Android payment app for the given payment method. | |
| 164 * Called when the app has been found to not have the right privileges t o handle this | |
| 165 * payment app. | |
| 166 * | |
| 167 * @param methodName The payment method name that the payment app offer s to handle. | |
| 168 * @param resolveInfo Identifying information for the native Android pay ment app. | |
| 169 */ | |
| 170 private void onInvalidApp(String methodName, ResolveInfo resolveInfo) { | |
| 171 removePendingApp(methodName, resolveInfo); | |
| 172 } | |
| 173 | |
| 174 /** Removes the method/app pair from the list of pending information to be verified. */ | |
| 175 private void removePendingApp(String methodName, ResolveInfo resolveInfo ) { | |
| 176 Set<ResolveInfo> pendingAppsForMethod = mPendingApps.get(methodName) ; | |
| 177 pendingAppsForMethod.remove(resolveInfo); | |
| 178 if (pendingAppsForMethod.isEmpty()) mPendingApps.remove(methodName); | |
| 179 if (mPendingApps.isEmpty()) onSearchFinished(); | |
| 180 } | |
| 181 | |
| 182 /** | |
| 183 * Disables invoking any native Android payment app for the given paymen t method. Called if | |
| 184 * unable to download or parse the payment method manifest. | |
| 185 * | |
| 186 * @param methodName The payment method name that has an invalid payment method manifest. | |
| 187 */ | |
| 188 private void onInvalidManifest(String methodName) { | |
| 189 mPendingApps.remove(methodName); | |
| 190 if (mPendingApps.isEmpty()) onSearchFinished(); | |
| 191 } | |
| 192 | |
| 193 /** | |
| 194 * Checks for IS_READY_TO_PAY service in each valid payment app and retu rns the valid apps | |
| 195 * to the caller. Called when finished verifying all payment methods and apps. | |
| 196 */ | |
| 197 private void onSearchFinished() { | |
| 198 List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryI ntentServices( | |
| 199 new Intent(ACTION_IS_READY_TO_PAY), 0); | |
|
gogerald1
2017/01/23 17:18:34
Could we do this when creating AndroidPaymentApp s
please use gerrit instead
2017/02/23 19:57:50
I don't see how that would improve efficiency. Ple
| |
| 200 for (int i = 0; i < resolveInfos.size(); i++) { | |
| 201 ResolveInfo resolveInfo = resolveInfos.get(i); | |
| 202 AndroidPaymentApp app = mResult.get(resolveInfo.serviceInfo.pack ageName); | |
| 203 if (app != null) { | |
| 204 app.setIsReadyToPayAction(resolveInfo.serviceInfo.name); | |
| 205 mCallback.onPaymentAppCreated(app); | |
|
gogerald1
2017/01/23 17:18:34
Is this means that the app must support ACTION_IS_
please use gerrit instead
2017/02/23 19:57:50
Fixed. It should be optional.
| |
| 206 } | |
| 207 } | |
| 208 | |
| 209 mCallback.onAllPaymentAppsCreated(); | |
| 210 } | |
| 211 | |
| 212 /** | |
| 213 * Verifies that the discovered native Android payment apps have the suf ficient privileges | |
| 214 * to handle a single payment method. Downloads and parses the manifest to compare package | |
| 215 * names, versions, and signatures to the apps. | |
| 216 */ | |
| 217 private class PaymentManifestVerifier | |
| 218 implements ManifestDownloadCallback, ManifestParseCallback { | |
| 219 private final String mMethodName; | |
| 220 private final List<AppInfo> mMatchingApps; | |
| 221 | |
| 222 /** Identifying information about an installed native Android paymen t app. */ | |
| 223 private class AppInfo { | |
| 224 /** Identifies a native Android payment app. */ | |
| 225 public ResolveInfo resolveInfo; | |
| 226 | |
| 227 /** The version code for the native Android payment app, e.g., 1 23. */ | |
| 228 public long version; | |
| 229 | |
| 230 /** | |
| 231 * The SHA256 certificate fingerprints for the native Android pa yment app, .e.g, | |
| 232 * ["308201dd30820146020101300d06092a864886f70d01010505003037311 630140"]. Order does | |
| 233 * not matter for comparison. | |
| 234 */ | |
| 235 public Set<String> sha256CertFingerprints; | |
| 236 } | |
| 237 | |
| 238 /** | |
| 239 * Builds the manifest verifier. | |
| 240 * | |
| 241 * @param methodName The name of the payment method name that apps offer to handle. | |
| 242 * Must be a valid URL that starts with "https:/ /". | |
| 243 * @param matchingApps The identifying information for the native An droid payment apps | |
| 244 * that offer to handle this payment method. | |
| 245 */ | |
| 246 public PaymentManifestVerifier(String methodName, List<ResolveInfo> matchingApps) { | |
| 247 assert methodName != null; | |
| 248 assert matchingApps != null; | |
| 249 mMethodName = methodName; | |
| 250 mMatchingApps = new ArrayList<>(); | |
| 251 for (int i = 0; i < matchingApps.size(); i++) { | |
| 252 AppInfo appInfo = new AppInfo(); | |
| 253 appInfo.resolveInfo = matchingApps.get(i); | |
| 254 mMatchingApps.add(appInfo); | |
| 255 } | |
| 256 } | |
| 257 | |
| 258 /** | |
| 259 * Begins the process of verifying that the discovered native Androi d payment apps have | |
| 260 * the sufficient privileges to handle this payment method. | |
| 261 */ | |
| 262 public void verify() { | |
| 263 ManifestDownloader.download(mMethodName, this); | |
| 264 } | |
| 265 | |
| 266 @Override | |
| 267 public void onManifestDownloadSuccess(String content) { | |
| 268 ManifestParser.parse(content, this); | |
| 269 } | |
| 270 | |
| 271 @Override | |
| 272 public void onManifestDownloadFailure() { | |
| 273 onInvalidManifest(mMethodName); | |
| 274 } | |
| 275 | |
| 276 @Override | |
| 277 public void onManifestParseSuccess(List<Manifest> manifests) { | |
| 278 for (int i = 0; i < manifests.size(); i++) { | |
| 279 Manifest manifest = manifests.get(i); | |
| 280 if ("*".equals(manifest.packageName)) { | |
|
gogerald1
2017/01/23 17:18:35
add comments to explain * means all payment apps a
please use gerrit instead
2017/02/23 19:57:50
Done.
| |
| 281 for (int j = 0; j < mMatchingApps.size(); j++) { | |
| 282 onValidPaymentApp(mMethodName, mMatchingApps.get(j). resolveInfo); | |
| 283 } | |
| 284 return; | |
| 285 } | |
| 286 } | |
| 287 | |
| 288 PackageManager pm = mContext.getPackageManager(); | |
| 289 for (int i = 0; i < mMatchingApps.size(); i++) { | |
| 290 AppInfo appInfo = mMatchingApps.get(i); | |
| 291 try { | |
| 292 PackageInfo packageInfo = | |
| 293 pm.getPackageInfo(appInfo.resolveInfo.activityIn fo.packageName, | |
| 294 PackageManager.GET_SIGNATURES); | |
| 295 appInfo.version = packageInfo.versionCode; | |
| 296 appInfo.sha256CertFingerprints = new HashSet<>(); | |
| 297 Signature[] signatures = packageInfo.signatures; | |
| 298 for (int j = 0; j < signatures.length; j++) { | |
| 299 appInfo.sha256CertFingerprints.add(signatures[j].toC harsString()); | |
| 300 } | |
| 301 } catch (NameNotFoundException e) { | |
| 302 // Leaving appInfo.sha256CertFingerprints uninitialized will call | |
| 303 // onInvalidApp() for this app below. | |
| 304 } | |
| 305 } | |
| 306 | |
| 307 for (int i = 0; i < mMatchingApps.size(); i++) { | |
| 308 AppInfo appInfo = mMatchingApps.get(i); | |
| 309 boolean isAllowed = false; | |
| 310 for (int j = 0; j < manifests.size(); j++) { | |
| 311 Manifest manifest = manifests.get(j); | |
| 312 if (appInfo.resolveInfo.activityInfo.packageName.equals( | |
| 313 manifest.packageName) | |
| 314 && appInfo.version >= manifest.version | |
| 315 && appInfo.sha256CertFingerprints != null | |
| 316 && appInfo.sha256CertFingerprints.equals( | |
| 317 manifest.sha256CertFingerprints)) { | |
| 318 onValidPaymentApp(mMethodName, appInfo.resolveInfo); | |
| 319 isAllowed = true; | |
| 320 break; | |
| 321 } | |
| 322 } | |
| 323 if (!isAllowed) onInvalidApp(mMethodName, appInfo.resolveInf o); | |
| 324 } | |
| 325 } | |
| 326 | |
| 327 @Override | |
| 328 public void onManifestParseFailure() { | |
| 329 onInvalidManifest(mMethodName); | |
| 330 } | |
| 331 } | |
| 81 } | 332 } |
| 82 } | 333 } |
| OLD | NEW |