Index: chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java |
index e3d4db7610798cb4e62bc732b3321090432775ec..31a571178940b42381eae6cd976d7926ac67c34d 100644 |
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java |
@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.payments; |
import android.content.pm.PackageInfo; |
import android.content.pm.ResolveInfo; |
import android.content.pm.Signature; |
+import android.support.annotation.Nullable; |
import org.chromium.base.Log; |
import org.chromium.chrome.browser.UrlConstants; |
@@ -20,6 +21,7 @@ import java.net.URI; |
import java.security.MessageDigest; |
import java.security.NoSuchAlgorithmException; |
import java.util.ArrayList; |
+import java.util.Arrays; |
import java.util.Formatter; |
import java.util.HashMap; |
import java.util.HashSet; |
@@ -35,7 +37,9 @@ import java.util.Set; |
* Spec: |
* https://docs.google.com/document/d/1izV4uC-tiRJG3JLooqY3YRLU22tYOsLTNq0P_InPJeE/edit#heading=h.cjp3jlnl47h5 |
*/ |
-public class PaymentManifestVerifier implements ManifestDownloadCallback, ManifestParseCallback { |
+public class PaymentManifestVerifier |
+ implements ManifestDownloadCallback, ManifestParseCallback, |
+ PaymentManifestWebDataService.PaymentManifestWebDataServiceCallback { |
private static final String TAG = "cr_PaymentManifest"; |
/** Interface for the callback to invoke when finished verification. */ |
@@ -67,6 +71,14 @@ public class PaymentManifestVerifier implements ManifestDownloadCallback, Manife |
* @param methodName The payment method name that has an invalid payment method manifest. |
*/ |
void onInvalidManifest(URI methodName); |
+ |
+ /** |
+ * Called when all the operations are done. After this call, the caller can release |
+ * resources used by this class. |
+ * |
+ * @param verifier The finished verifier. |
+ */ |
+ void onVerifyFinished(PaymentManifestVerifier verifier); |
} |
/** Identifying information about an installed native Android payment app. */ |
@@ -90,6 +102,16 @@ public class PaymentManifestVerifier implements ManifestDownloadCallback, Manife |
/** A mapping from the package name to the application that matches the method name. */ |
private final Map<String, AppInfo> mMatchingApps; |
+ /** A list of package names of the apps that have been verified by using the cached manifest. */ |
+ private final List<String> mVerifiedAppPackageNamesByCachedManifest; |
+ |
+ /** A set of package names of the apps which support the payment method. */ |
+ private final Set<String> mSupportedAppPackageNames; |
+ |
+ /** A list of manifests of the apps which support the payment method. */ |
+ private final List<WebAppManifestSection[]> mSupportedAppParsedManifests; |
+ |
+ private final PaymentManifestWebDataService mWebDataService; |
private final PaymentManifestParser mParser; |
private final PackageManagerDelegate mPackageManagerDelegate; |
private final ManifestVerifyCallback mCallback; |
@@ -97,6 +119,9 @@ public class PaymentManifestVerifier implements ManifestDownloadCallback, Manife |
private int mPendingWebAppManifestsCount; |
private boolean mAtLeastOneManifestFailedToDownloadOrParse; |
+ /** Whether the manifest cache is stale (unusable). */ |
+ private boolean mIsManifestCacheStaleOrUnusable; |
+ |
/** |
* Builds the manifest verifier. |
* |
@@ -104,14 +129,16 @@ public class PaymentManifestVerifier implements ManifestDownloadCallback, Manife |
* Must be an absolute URI with HTTPS scheme. |
* @param matchingApps The identifying information for the native Android payment apps |
* that offer to handle this payment method. |
+ * @param webDataService The web data service to cache manifest. |
* @param downloader The manifest downloader. |
* @param parser The manifest parser. |
* @param packageManagerDelegate The package information retriever. |
* @param callback The callback to be notified of verification result. |
*/ |
public PaymentManifestVerifier(URI methodName, List<ResolveInfo> matchingApps, |
- PaymentManifestDownloader downloader, PaymentManifestParser parser, |
- PackageManagerDelegate packageManagerDelegate, ManifestVerifyCallback callback) { |
+ PaymentManifestWebDataService webDataService, PaymentManifestDownloader downloader, |
+ PaymentManifestParser parser, PackageManagerDelegate packageManagerDelegate, |
+ ManifestVerifyCallback callback) { |
assert methodName.isAbsolute(); |
assert UrlConstants.HTTPS_SCHEME.equals(methodName.getScheme()); |
assert !matchingApps.isEmpty(); |
@@ -125,6 +152,7 @@ public class PaymentManifestVerifier implements ManifestDownloadCallback, Manife |
} |
mDownloader = downloader; |
+ mWebDataService = webDataService; |
mParser = parser; |
mPackageManagerDelegate = packageManagerDelegate; |
mCallback = callback; |
@@ -137,6 +165,10 @@ public class PaymentManifestVerifier implements ManifestDownloadCallback, Manife |
Log.d(TAG, "Unable to generate SHA-256 hashes."); |
} |
mMessageDigest = md; |
+ |
+ mVerifiedAppPackageNamesByCachedManifest = new ArrayList<>(); |
+ mSupportedAppPackageNames = new HashSet<>(); |
+ mSupportedAppParsedManifests = new ArrayList<>(); |
} |
/** |
@@ -146,6 +178,7 @@ public class PaymentManifestVerifier implements ManifestDownloadCallback, Manife |
public void verify() { |
if (mMessageDigest == null) { |
mCallback.onInvalidManifest(mMethodName); |
+ mCallback.onVerifyFinished(this); |
return; |
} |
@@ -177,7 +210,17 @@ public class PaymentManifestVerifier implements ManifestDownloadCallback, Manife |
mMatchingApps.remove(invalidAppsToRemove.get(i)); |
} |
- if (!mMatchingApps.isEmpty()) mDownloader.downloadPaymentMethodManifest(mMethodName, this); |
+ if (mMatchingApps.isEmpty()) { |
+ mCallback.onInvalidManifest(mMethodName); |
+ mCallback.onVerifyFinished(this); |
+ return; |
+ } |
+ |
+ // Try to fetch manifest from the cache first. |
+ if (!mWebDataService.getPaymentMethodManifest(mMethodName.toString(), this)) { |
+ mIsManifestCacheStaleOrUnusable = true; |
+ mDownloader.downloadPaymentMethodManifest(mMethodName, this); |
+ } |
} |
/** |
@@ -201,6 +244,64 @@ public class PaymentManifestVerifier implements ManifestDownloadCallback, Manife |
} |
@Override |
+ public void onPaymentMethodManifestFetched(String[] appPackageNames) { |
+ Set<String> fetchedApps = new HashSet<>(Arrays.asList(appPackageNames)); |
+ Set<String> matchingApps = mMatchingApps.keySet(); |
+ // The cache may be stale if it doesn't contain all matching apps, so switch to download the |
+ // manifest online immediately. |
+ if (!fetchedApps.containsAll(matchingApps)) { |
+ mIsManifestCacheStaleOrUnusable = true; |
+ mDownloader.downloadPaymentMethodManifest(mMethodName, this); |
+ return; |
+ } |
+ |
+ mPendingWebAppManifestsCount = matchingApps.size(); |
+ for (int i = 0; i < appPackageNames.length; i++) { |
+ if (!mWebDataService.getPaymentWebAppManifest(appPackageNames[i], this)) { |
+ mIsManifestCacheStaleOrUnusable = true; |
+ mPendingWebAppManifestsCount = 0; |
+ mDownloader.downloadPaymentMethodManifest(mMethodName, this); |
+ return; |
+ } |
+ } |
+ } |
+ |
+ @Override |
+ public void onPaymentWebAppManifestFetched(WebAppManifestSection[] manifest) { |
+ if (mIsManifestCacheStaleOrUnusable) return; |
+ |
+ if (manifest == null || manifest.length == 0) { |
+ mIsManifestCacheStaleOrUnusable = true; |
+ mPendingWebAppManifestsCount = 0; |
+ mDownloader.downloadPaymentMethodManifest(mMethodName, this); |
+ return; |
+ } |
+ |
+ String verifiedAppPackageName = verifyAppWithWebAppManifest(manifest); |
+ if (verifiedAppPackageName != null) { |
+ // Do not notify onValidPaymentApp immediately in case of fetching the other web app's |
+ // manifest failed. Switch to download manifest online in that case immediately. |
+ mVerifiedAppPackageNamesByCachedManifest.add(verifiedAppPackageName); |
+ } |
+ |
+ mPendingWebAppManifestsCount--; |
+ if (mPendingWebAppManifestsCount != 0) return; |
+ |
+ for (int i = 0; i < mVerifiedAppPackageNamesByCachedManifest.size(); i++) { |
+ String appPackageName = mVerifiedAppPackageNamesByCachedManifest.get(i); |
+ mCallback.onValidPaymentApp(mMethodName, mMatchingApps.get(appPackageName).resolveInfo); |
+ mMatchingApps.remove(appPackageName); |
+ } |
+ |
+ for (Map.Entry<String, AppInfo> entry : mMatchingApps.entrySet()) { |
+ mCallback.onInvalidPaymentApp(mMethodName, entry.getValue().resolveInfo); |
+ } |
+ |
+ // Download and parse manifest to refresh cache. |
+ mDownloader.downloadPaymentMethodManifest(mMethodName, this); |
+ } |
+ |
+ @Override |
public void onPaymentMethodManifestDownloadSuccess(String content) { |
mParser.parsePaymentMethodManifest(content, this); |
} |
@@ -233,6 +334,46 @@ public class PaymentManifestVerifier implements ManifestDownloadCallback, Manife |
if (mAtLeastOneManifestFailedToDownloadOrParse) return; |
+ for (int i = 0; i < manifest.length; i++) { |
+ mSupportedAppPackageNames.add(manifest[i].id); |
+ } |
+ mSupportedAppParsedManifests.add(manifest); |
+ |
+ // Do not verify payment app if it has already been verified by cached manifest. |
+ if (mIsManifestCacheStaleOrUnusable) { |
+ String verifiedAppPackageName = verifyAppWithWebAppManifest(manifest); |
+ if (verifiedAppPackageName != null) { |
+ mCallback.onValidPaymentApp( |
+ mMethodName, mMatchingApps.get(verifiedAppPackageName).resolveInfo); |
+ mMatchingApps.remove(verifiedAppPackageName); |
+ } |
+ } |
+ |
+ mPendingWebAppManifestsCount--; |
+ if (mPendingWebAppManifestsCount != 0) return; |
+ |
+ // Do not notify onInvalidPaymentApp if it has already be notified when checking by cached |
+ // manifest. |
+ if (mIsManifestCacheStaleOrUnusable) { |
+ for (Map.Entry<String, AppInfo> entry : mMatchingApps.entrySet()) { |
+ mCallback.onInvalidPaymentApp(mMethodName, entry.getValue().resolveInfo); |
+ } |
+ } |
+ |
+ // Cache supported apps' package names. |
+ mWebDataService.addPaymentMethodManifest(mMethodName.toString(), |
+ mSupportedAppPackageNames.toArray(new String[mSupportedAppPackageNames.size()])); |
+ |
+ // Cache supported apps' parsed manifests. |
+ for (int i = 0; i < mSupportedAppParsedManifests.size(); i++) { |
+ mWebDataService.addPaymentWebAppManifest(mSupportedAppParsedManifests.get(i)); |
+ } |
+ |
+ mCallback.onVerifyFinished(this); |
+ } |
+ |
+ @Nullable |
+ private String verifyAppWithWebAppManifest(WebAppManifestSection[] manifest) { |
List<Set<String>> sectionsFingerprints = new ArrayList<>(); |
for (int i = 0; i < manifest.length; i++) { |
WebAppManifestSection section = manifest[i]; |
@@ -249,18 +390,11 @@ public class PaymentManifestVerifier implements ManifestDownloadCallback, Manife |
if (appInfo != null && appInfo.version >= section.minVersion |
&& appInfo.sha256CertFingerprints != null |
&& appInfo.sha256CertFingerprints.equals(sectionsFingerprints.get(i))) { |
- mCallback.onValidPaymentApp(mMethodName, appInfo.resolveInfo); |
- mMatchingApps.remove(section.id); |
- break; |
+ return section.id; |
} |
} |
- mPendingWebAppManifestsCount--; |
- if (mPendingWebAppManifestsCount == 0) { |
- for (Map.Entry<String, AppInfo> entry : mMatchingApps.entrySet()) { |
- mCallback.onInvalidPaymentApp(mMethodName, entry.getValue().resolveInfo); |
- } |
- } |
+ return null; |
} |
@Override |
@@ -268,7 +402,8 @@ public class PaymentManifestVerifier implements ManifestDownloadCallback, Manife |
if (mAtLeastOneManifestFailedToDownloadOrParse) return; |
mAtLeastOneManifestFailedToDownloadOrParse = true; |
- mCallback.onInvalidManifest(mMethodName); |
+ if (mIsManifestCacheStaleOrUnusable) mCallback.onInvalidManifest(mMethodName); |
+ mCallback.onVerifyFinished(this); |
} |
@Override |
@@ -276,6 +411,7 @@ public class PaymentManifestVerifier implements ManifestDownloadCallback, Manife |
if (mAtLeastOneManifestFailedToDownloadOrParse) return; |
mAtLeastOneManifestFailedToDownloadOrParse = true; |
- mCallback.onInvalidManifest(mMethodName); |
+ if (mIsManifestCacheStaleOrUnusable) mCallback.onInvalidManifest(mMethodName); |
+ mCallback.onVerifyFinished(this); |
} |
} |