Commit 70f24bcb authored by Liquan (Max) Gu's avatar Liquan (Max) Gu Committed by Commit Bot

Reland "Reland "[PlayBilling] Support app-store billing in AndroidPaymentAppFinder""

Original patch: https://crrev.com/c/2141209
Revert: https://crrev.com/c/2142334
 Reason for revert:
 ErrorProne usage of array.toString() blocks compilation.

Reason for reland:
 The error get fixed.
 Before: array.toString()
 After: Arrays.toString(array)

Change-Id: Id669ad925c980584911809d83dd10506e0fa9e27
Bug: 1064740
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2141244Reviewed-by: default avatarRouslan Solomakhin <rouslan@chromium.org>
Reviewed-by: default avatarLiquan (Max) Gu <maxlg@chromium.org>
Commit-Queue: Liquan (Max) Gu <maxlg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#757677}
parent a0e00d22
......@@ -15,7 +15,6 @@ import androidx.annotation.VisibleForTesting;
import org.chromium.base.Log;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.payments.PaymentManifestVerifier.ManifestVerifyCallback;
import org.chromium.components.payments.MethodStrings;
import org.chromium.components.payments.PaymentManifestDownloader;
......@@ -23,6 +22,7 @@ import org.chromium.components.payments.PaymentManifestParser;
import org.chromium.components.payments.intent.WebPaymentIntentHelper;
import org.chromium.payments.mojom.PaymentDetailsModifier;
import org.chromium.payments.mojom.PaymentMethodData;
import org.chromium.url.GURL;
import org.chromium.url.URI;
import java.util.ArrayList;
......@@ -69,12 +69,6 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
/* package */ static final String META_DATA_NAME_OF_SUPPORTED_DELEGATIONS =
"org.chromium.payment_supported_delegations";
/*
* The ignored payment method identifiers. Payment apps with this payment method identifier are
* ignored.
*/
private final Set<String> mIgnoredMethods = new HashSet<>();
private final Set<String> mNonUriPaymentMethods = new HashSet<>();
private final Set<URI> mUriPaymentMethods = new HashSet<>();
private final PaymentManifestDownloader mDownloader;
......@@ -86,10 +80,13 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
private final boolean mIsIncognito;
/**
* A map from an app-store app's package name to its billing method. All of the supported
* app-store billing method must insert an entry to this map.
* The app stores that supports app-store billing methods.
*
* key: the app-store app's package name, e.g., "com.google.vendor" (Google Play Store).
* value: the app-store app's billing method identifier, e.g.,
* "https://play.google.com/billing". Only valid GURLs are allowed.
*/
private final Map<String, String> mAppStoreBillingMethodMap = new HashMap();
private final Map<String, GURL> mAppStores = new HashMap();
/**
* A mapping from an Android package name to the payment app with that package name. The apps
......@@ -173,9 +170,10 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
PaymentAppFactoryInterface factory) {
mDelegate = delegate;
mIgnoredMethods.add(MethodStrings.GOOGLE_PLAY_BILLING);
mAppStoreBillingMethodMap.put(PLAY_STORE_PACKAGE_NAME, MethodStrings.GOOGLE_PLAY_BILLING);
mAppStores.put(PLAY_STORE_PACKAGE_NAME, new GURL(MethodStrings.GOOGLE_PLAY_BILLING));
for (GURL method : mAppStores.values()) {
assert method.isValid();
}
mDownloader = downloader;
mWebDataService = webDataService;
......@@ -187,19 +185,62 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
mIsIncognito = activity != null && activity.getCurrentTabModel().isIncognito();
}
private boolean isInTwaInstalledFromAppStore() {
ChromeActivity activity =
ChromeActivity.fromWebContents(mDelegate.getParams().getWebContents());
if (activity == null) return false;
if (!(activity instanceof CustomTabActivity)) return false;
CustomTabActivity customTabActivity = ((CustomTabActivity) activity);
if (!customTabActivity.isInTwaMode()) return false;
String twaPackageName = customTabActivity.getTwaPackage();
private boolean isInTwaInstalledFromAppStore(ChromeActivity activity) {
assert activity != null;
String twaPackageName = mPackageManagerDelegate.getTwaPackageName(activity);
if (twaPackageName == null) return false;
String installerPackageName =
activity.getPackageManager().getInstallerPackageName(twaPackageName);
String installerPackageName = mPackageManagerDelegate.getInstallerPackage(twaPackageName);
if (installerPackageName == null) return false;
return mAppStoreBillingMethodMap.keySet().contains(installerPackageName);
return mAppStores.containsKey(installerPackageName);
}
/** Precondition: {@link #isInTwaInstalledFromAppStore} returns true. */
private void findAppStoreBillingApp(
ChromeActivity activity, List<ResolveInfo> allInstalledPaymentApps) {
assert activity != null;
// The following asserts are assumed to have been checked in {@link
// isInTwaInstalledFromAppStore}.
String twaPackageName = mPackageManagerDelegate.getTwaPackageName(activity);
assert twaPackageName != null;
String installerAppStorePackageName =
mPackageManagerDelegate.getInstallerPackage(twaPackageName);
assert installerAppStorePackageName != null;
GURL appStoreBillingUriMethod = mAppStores.get(installerAppStorePackageName);
assert appStoreBillingUriMethod != null;
assert appStoreBillingUriMethod.isValid();
String appStoreBillingMethod = appStoreBillingUriMethod.getSpec();
if (!mDelegate.getParams().getMethodData().containsKey(appStoreBillingMethod)) return;
ResolveInfo twaApp = findAppWithPackageNameAndSupportedMethod(
allInstalledPaymentApps, twaPackageName, appStoreBillingUriMethod);
if (twaApp == null) {
android.util.Log.d(TAG, "The current TWA cannot handle Payment Request.");
return;
}
onValidPaymentAppForPaymentMethodName(twaApp, appStoreBillingMethod);
}
private ResolveInfo findAppWithPackageNameAndSupportedMethod(
List<ResolveInfo> apps, String packageName, GURL uriMethod) {
assert packageName != null;
assert uriMethod != null;
for (int i = 0; i < apps.size(); i++) {
ResolveInfo app = apps.get(i);
String appPackageName = app.activityInfo.packageName;
if (!packageName.equals(appPackageName)) continue;
String defaultMethod = app.activityInfo.metaData == null
? null
: app.activityInfo.metaData.getString(
META_DATA_NAME_OF_DEFAULT_PAYMENT_METHOD_NAME);
GURL defaultUriMethod = new GURL(defaultMethod);
if ((uriMethod.isValid()
&& getSupportedPaymentMethods(app.activityInfo)
.contains(uriMethod.getSpec()))
|| (defaultUriMethod.isValid() && uriMethod.equals(defaultUriMethod))) {
return app;
}
}
return null;
}
/**
......@@ -218,7 +259,7 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
for (String method : mDelegate.getParams().getMethodData().keySet()) {
assert !TextUtils.isEmpty(method);
if (mIgnoredMethods.contains(method)) continue;
if (mAppStores.containsValue(new GURL(method))) continue;
if (supportedNonUriPaymentMethods.contains(method)) {
mNonUriPaymentMethods.add(method);
} else if (UriUtils.looksLikeUriMethod(method)) {
......@@ -245,9 +286,17 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
}
}
if (isInTwaInstalledFromAppStore()) {
// TODO(crbug.com/1064740): the finder would special-case the TWA installed from App
// Store to return only the app-store app.
// WebContents is possible to attach to different activities on {@link PaymentRequest}
// created and shown. Ideally {@link #findAppStoreBillingApp} should have based on the
// activity that is used when PaymentRequest is shown. But we intentionally not do that for
// the sake of simple design and better performance. Plus, for app store billing case in
// particular, it's unusual for a TWA to switch to CCT without destroying JavaScript context
// and, consequently, the {@link PaymentRequest} object.
ChromeActivity activity =
ChromeActivity.fromWebContents(mDelegate.getParams().getWebContents());
if (!mDelegate.getParams().requestShippingOrPayerContact() && activity != null
&& isInTwaInstalledFromAppStore(activity)) {
findAppStoreBillingApp(activity, allInstalledPaymentApps);
}
// All URI methods for which manifests should be downloaded. For example, if merchant
......@@ -646,14 +695,14 @@ public class AndroidPaymentAppFinder implements ManifestVerifyCallback {
}
/**
* Ignores the given payment method identifier, so no Android payment apps for this method are
* looked up in findAndroidPaymentApps(). Calling this multiple times will union the new payment
* methods with the existing set.
* Add an app store for testing.
*
* @param ignoredPaymentMethodIdentifier The ignored payment method identifier.
* @param packageName The package name of the app store.
* @param paymentMethod The payment method identifier of the app store.
*/
@VisibleForTesting
/* package */ void ignorePaymentMethodForTest(String ignoredPaymentMethodIdentifier) {
mIgnoredMethods.add(ignoredPaymentMethodIdentifier);
/* package */ void addAppStoreForTest(String packageName, GURL paymentMethod) {
assert paymentMethod.isValid();
mAppStores.put(packageName, paymentMethod);
}
}
......@@ -20,6 +20,8 @@ import androidx.annotation.Nullable;
import org.chromium.base.ContextUtils;
import org.chromium.base.PackageManagerUtils;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import java.util.List;
......@@ -123,4 +125,31 @@ public class PackageManagerDelegate {
}
return resources == null ? null : resources.getStringArray(resourceId);
}
/**
* Get the package name of an activity if it is a Trusted Web Activity.
* @param activity An activity that is intended to check whether its a Trusted Web Activity and
* get the package name from. Not allowed to be null.
* @return The package name of a given activity if it is a Trusted Web Activity; null otherwise.
*/
@Nullable
public String getTwaPackageName(ChromeActivity activity) {
assert activity != null;
if (!(activity instanceof CustomTabActivity)) return null;
CustomTabActivity customTabActivity = ((CustomTabActivity) activity);
if (!customTabActivity.isInTwaMode()) return null;
return customTabActivity.getTwaPackage();
}
/**
* Get the package name of a specified package's installer app.
* @param packageName The package name of the specified package. Not allowed to be null.
* @return The package name of the installer app.
*/
@Nullable
public String getInstallerPackage(String packageName) {
assert packageName != null;
return ContextUtils.getApplicationContext().getPackageManager().getInstallerPackageName(
packageName);
}
}
......@@ -95,4 +95,12 @@ public interface PaymentAppFactoryParams {
default String getTotalAmountCurrency() {
return null;
}
/**
* @return Whether the PaymentRequest is requesting delegation of either shipping or payer
* contact.
*/
default boolean requestShippingOrPayerContact() {
return false;
}
}
......@@ -2620,6 +2620,12 @@ public class PaymentRequestImpl
return mRawTotal.amount.currency;
}
// PaymentAppFactoryParams implementation.
@Override
public boolean requestShippingOrPayerContact() {
return mRequestShipping || mRequestPayerName || mRequestPayerPhone || mRequestPayerEmail;
}
// PaymentAppFactoryDelegate implementation.
@Override
public PaymentAppFactoryParams getParams() {
......
......@@ -16,6 +16,8 @@ import android.os.Bundle;
import androidx.annotation.Nullable;
import org.chromium.chrome.browser.ChromeActivity;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
......@@ -31,6 +33,10 @@ class MockPackageManagerDelegate extends PackageManagerDelegate {
private final List<ResolveInfo> mServices = new ArrayList<>();
private final Map<ApplicationInfo, String[]> mResources = new HashMap<>();
private String mMockTwaPackage;
// A map of a package name to its installer's package name.
private Map<String, String> mMockInstallerPackageMap = new HashMap<>();
/**
* Simulates an installed payment app with no supported delegations.
*
......@@ -140,6 +146,27 @@ class MockPackageManagerDelegate extends PackageManagerDelegate {
mLabels.clear();
}
/**
* Mock the current package to be a Trust Web Activity package.
* @param mockTwaPackage The intended package nam, not allowed to be null.
*/
public void setMockTrustedWebActivity(String mockTwaPackage) {
assert mockTwaPackage != null;
mMockTwaPackage = mockTwaPackage;
}
/**
* Mock the installer of a specified package.
* @param packageName The package name that is intended to mock a installer for.
* @param installerPackageName The package name intended to be set as the installer of the
* specified package. not allowed to be null.
*/
public void mockInstallerForPackage(String packageName, String installerPackageName) {
assert installerPackageName != null;
assert packageName != null;
mMockInstallerPackageMap.put(packageName, installerPackageName);
}
@Override
public List<ResolveInfo> getActivitiesThatCanRespondToIntentWithMetaData(Intent intent) {
return mActivities;
......@@ -177,4 +204,17 @@ class MockPackageManagerDelegate extends PackageManagerDelegate {
assert STRING_ARRAY_RESOURCE_ID == resourceId;
return mResources.get(applicationInfo);
}
@Override
@Nullable
public String getInstallerPackage(String packageName) {
return !mMockInstallerPackageMap.isEmpty() ? mMockInstallerPackageMap.get(packageName)
: super.getInstallerPackage(packageName);
}
@Override
@Nullable
public String getTwaPackageName(ChromeActivity activity) {
return mMockTwaPackage != null ? mMockTwaPackage : super.getTwaPackageName(activity);
}
}
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment