Skip to content

Commit

Permalink
[PRImpl] Move PaymentAppComparator into its own file
Browse files Browse the repository at this point in the history
Before:
* The implementation of PaymentAppComparator lived in
PaymentRequestImpl.
* Its internal methods were visible to the whole PaymentRequestImpl.
* Although it only requires 4 parameters of PaymentRequestImpl, it
depends on the whole PaymentRequestImpl.

After:
* The implementation of PaymentAppComparator lived in a separate file.
* Its internal methods are not visible to PaymentRequestImpl.
* Its dependencies on PaymentRequestImpl are explicitly passed in
through the constructor.

Bug: 1102522

Change-Id: Ia8cb0611de83a2ef5b7d97943e3334156d2bb08f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2299857
Commit-Queue: Liquan (Max) Gu <maxlg@chromium.org>
Reviewed-by: Rouslan Solomakhin <rouslan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#789068}
  • Loading branch information
maxlgu authored and Commit Bot committed Jul 16, 2020
1 parent f90faee commit 0abb2e3
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 92 deletions.
1 change: 1 addition & 0 deletions chrome/android/chrome_java_sources.gni
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/payments/CanMakePaymentQuery.java",
"java/src/org/chromium/chrome/browser/payments/CardEditor.java",
"java/src/org/chromium/chrome/browser/payments/ContactEditor.java",
"java/src/org/chromium/chrome/browser/payments/PaymentAppComparator.java",
"java/src/org/chromium/chrome/browser/payments/PaymentAppFactoryDelegate.java",
"java/src/org/chromium/chrome/browser/payments/PaymentAppFactoryInterface.java",
"java/src/org/chromium/chrome/browser/payments/PaymentAppFactoryParams.java",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.payments;

import org.chromium.components.autofill.Completable;
import org.chromium.components.payments.PaymentApp;

import java.util.Comparator;

/** A comparator that is used to rank the payment apps to be listed in the payment sheet. */
/* package */ class PaymentAppComparator implements Comparator<PaymentApp> {
private final ParamsProvider mParamsProvider;

/** The provider of the parameters needed by this class. */
/* package */ interface ParamsProvider {
/** @return The requestShipping set by the merchant. */
boolean requestShipping();
/** @return The requestPayerName set by the merchant. */
boolean requestPayerName();
/** @return The requestPayerEmail set by the merchant. */
boolean requestPayerEmail();
/** @return The requestPayerPhone set by the merchant. */
boolean requestPayerPhone();
}

/**
* Create an instance of PaymentAppComparator.
* @param paramsProvider The provider of the params needed by this class.
*/
/* package */ PaymentAppComparator(ParamsProvider paramsProvider) {
mParamsProvider = paramsProvider;
}

/**
* Compares two payment apps by frecency.
* Return negative value if a has strictly lower frecency score than b.
* Return zero if a and b have the same frecency score.
* Return positive value if a has strictly higher frecency score than b.
*/
private static int compareAppsByFrecency(PaymentApp a, PaymentApp b) {
int aCount = PaymentPreferencesUtil.getPaymentAppUseCount(a.getIdentifier());
int bCount = PaymentPreferencesUtil.getPaymentAppUseCount(b.getIdentifier());
long aDate = PaymentPreferencesUtil.getPaymentAppLastUseDate(a.getIdentifier());
long bDate = PaymentPreferencesUtil.getPaymentAppLastUseDate(a.getIdentifier());

return Double.compare(getFrecencyScore(aCount, aDate), getFrecencyScore(bCount, bDate));
}

/**
* Compares two Completable by completeness score.
* Return negative value if a has strictly lower completeness score than b.
* Return zero if a and b have the same completeness score.
* Return positive value if a has strictly higher completeness score than b.
*/
/* package */ static int compareCompletablesByCompleteness(Completable a, Completable b) {
return Integer.compare(a.getCompletenessScore(), b.getCompletenessScore());
}

/**
* The frecency score is calculated according to use count and last use date. The formula is
* the same as the one used in GetFrecencyScore in autofill_data_model.cc.
*/
private static double getFrecencyScore(int count, long date) {
long currentTime = System.currentTimeMillis();
return -Math.log((currentTime - date) / (24 * 60 * 60 * 1000) + 2) / Math.log(count + 2);
}

/**
* Sorts the payment apps by several rules:
* Rule 1: Non-autofill before autofill.
* Rule 2: Complete apps before incomplete apps.
* Rule 3: When shipping address is requested, apps which will handle shipping address before
* others.
* Rule 4: When payer's contact information is requested, apps which will handle more required
* contact fields (name, email, phone) come before others.
* Rule 5: Preselectable apps before non-preselectable apps.
* Rule 6: Frequently and recently used apps before rarely and non-recently used apps.
*/
@Override
public int compare(PaymentApp a, PaymentApp b) {
// Non-autofill apps first.
int autofill = (a.isAutofillInstrument() ? 1 : 0) - (b.isAutofillInstrument() ? 1 : 0);
if (autofill != 0) return autofill;

// Complete cards before cards with missing information.
int completeness = compareCompletablesByCompleteness(b, a);
if (completeness != 0) return completeness;

// Payment apps which handle shipping address before others.
if (mParamsProvider.requestShipping()) {
int canHandleShipping =
(b.handlesShippingAddress() ? 1 : 0) - (a.handlesShippingAddress() ? 1 : 0);
if (canHandleShipping != 0) return canHandleShipping;
}

// Payment apps which handle more contact information fields come first.
int aSupportedContactDelegationsNum = 0;
int bSupportedContactDelegationsNum = 0;
if (mParamsProvider.requestPayerName()) {
if (a.handlesPayerName()) aSupportedContactDelegationsNum++;
if (b.handlesPayerName()) bSupportedContactDelegationsNum++;
}
if (mParamsProvider.requestPayerEmail()) {
if (a.handlesPayerEmail()) aSupportedContactDelegationsNum++;
if (b.handlesPayerEmail()) bSupportedContactDelegationsNum++;
}
if (mParamsProvider.requestPayerPhone()) {
if (a.handlesPayerPhone()) aSupportedContactDelegationsNum++;
if (b.handlesPayerPhone()) bSupportedContactDelegationsNum++;
}
if (bSupportedContactDelegationsNum != aSupportedContactDelegationsNum) {
return bSupportedContactDelegationsNum - aSupportedContactDelegationsNum > 0 ? 1 : -1;
}

// Preselectable apps before non-preselectable apps.
// Note that this only affects service worker payment apps' apps for now
// since autofill cards have already been sorted by preselect after sorting by completeness.
// And the other payment apps can always be preselected.
int canPreselect = (b.canPreselect() ? 1 : 0) - (a.canPreselect() ? 1 : 0);
if (canPreselect != 0) return canPreselect;

// More frequently and recently used apps first.
return compareAppsByFrecency(b, a);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ public class PaymentRequestImpl
PaymentApp.InstrumentDetailsCallback,
PaymentResponseHelper.PaymentResponseRequesterDelegate, FocusChangedObserver,
NormalizedAddressRequestDelegate, SettingsAutofillAndPaymentsObserver.Observer,
PaymentDetailsConverter.MethodChecker, PaymentHandlerUiObserver {
PaymentDetailsConverter.MethodChecker, PaymentHandlerUiObserver,
PaymentAppComparator.ParamsProvider {
/**
* A delegate to ask questions about the system, that allows tests to inject behaviour without
* having to modify the entire system. This partially mirrors a similar C++
Expand Down Expand Up @@ -262,7 +263,7 @@ public interface NativeObserverForTest {
private static final String TAG = "PaymentRequest";
// Reverse order of the comparator to sort in descending order of completeness scores.
private static final Comparator<Completable> COMPLETENESS_COMPARATOR =
(a, b) -> (compareCompletablesByCompleteness(b, a));
(a, b) -> (PaymentAppComparator.compareCompletablesByCompleteness(b, a));

private ComponentPaymentRequestImpl mComponentPaymentRequestImpl;

Expand All @@ -272,62 +273,7 @@ public interface NativeObserverForTest {
private boolean mRequestPayerPhone;
private boolean mRequestPayerEmail;

/**
* Sorts the payment apps by several rules:
* Rule 1: Non-autofill before autofill.
* Rule 2: Complete apps before incomplete apps.
* Rule 3: When shipping address is requested, apps which will handle shipping address before
* others.
* Rule 4: When payer's contact information is requested, apps which will handle more required
* contact fields (name, email, phone) come before others.
* Rule 5: Preselectable apps before non-preselectable apps.
* Rule 6: Frequently and recently used apps before rarely and non-recently used apps.
*/
private final Comparator<PaymentApp> mPaymentAppComparator = (a, b) -> {
// Non-autofill apps first.
int autofill = (a.isAutofillInstrument() ? 1 : 0) - (b.isAutofillInstrument() ? 1 : 0);
if (autofill != 0) return autofill;

// Complete cards before cards with missing information.
int completeness = compareCompletablesByCompleteness(b, a);
if (completeness != 0) return completeness;

// Payment apps which handle shipping address before others.
if (mRequestShipping) {
int canHandleShipping =
(b.handlesShippingAddress() ? 1 : 0) - (a.handlesShippingAddress() ? 1 : 0);
if (canHandleShipping != 0) return canHandleShipping;
}

// Payment apps which handle more contact information fields come first.
int aSupportedContactDelegationsNum = 0;
int bSupportedContactDelegationsNum = 0;
if (mRequestPayerName) {
if (a.handlesPayerName()) aSupportedContactDelegationsNum++;
if (b.handlesPayerName()) bSupportedContactDelegationsNum++;
}
if (mRequestPayerEmail) {
if (a.handlesPayerEmail()) aSupportedContactDelegationsNum++;
if (b.handlesPayerEmail()) bSupportedContactDelegationsNum++;
}
if (mRequestPayerPhone) {
if (a.handlesPayerPhone()) aSupportedContactDelegationsNum++;
if (b.handlesPayerPhone()) bSupportedContactDelegationsNum++;
}
if (bSupportedContactDelegationsNum != aSupportedContactDelegationsNum) {
return bSupportedContactDelegationsNum - aSupportedContactDelegationsNum > 0 ? 1 : -1;
}

// Preselectable apps before non-preselectable apps.
// Note that this only affects service worker payment apps' apps for now
// since autofill cards have already been sorted by preselect after sorting by completeness.
// And the other payment apps can always be preselected.
int canPreselect = (b.canPreselect() ? 1 : 0) - (a.canPreselect() ? 1 : 0);
if (canPreselect != 0) return canPreselect;

// More frequently and recently used apps first.
return compareAppsByFrecency(b, a);
};
private final Comparator<PaymentApp> mPaymentAppComparator;

private static PaymentRequestServiceObserverForTest sObserverForTest;
private static boolean sIsLocalCanMakePaymentQueryQuotaEnforcedForTest;
Expand Down Expand Up @@ -567,6 +513,7 @@ public PaymentRequestImpl(RenderFrameHost renderFrameHost, Delegate delegate,

if (sObserverForTest != null) sObserverForTest.onPaymentRequestCreated(this);
mPaymentUIsManager = new PaymentUIsManager();
mPaymentAppComparator = new PaymentAppComparator(/*paramsProvider=*/this);
}

// Implement ComponentPaymentRequestDelegate:
Expand Down Expand Up @@ -1295,6 +1242,30 @@ private boolean dismissMinimalUIForTestInternal() {
return true;
}

// Implement PaymentAppComparator.ParamsProvider:
@Override
public boolean requestShipping() {
return mRequestShipping;
}

// Implement PaymentAppComparator.ParamsProvider:
@Override
public boolean requestPayerName() {
return mRequestPayerName;
}

// Implement PaymentAppComparator.ParamsProvider:
@Override
public boolean requestPayerEmail() {
return mRequestPayerEmail;
}

// Implement PaymentAppComparator.ParamsProvider:
@Override
public boolean requestPayerPhone() {
return mRequestPayerPhone;
}

/**
* Called to open a new PaymentHandler UI on the showing PaymentRequest.
* @param url The url of the payment app to be displayed in the UI.
Expand Down Expand Up @@ -3124,40 +3095,6 @@ public static void setIsLocalCanMakePaymentQueryQuotaEnforcedForTest() {
mSkipUiForNonUrlPaymentMethodIdentifiers = true;
}

/**
* Compares two payment apps by frecency.
* Return negative value if a has strictly lower frecency score than b.
* Return zero if a and b have the same frecency score.
* Return positive value if a has strictly higher frecency score than b.
*/
private static int compareAppsByFrecency(PaymentApp a, PaymentApp b) {
int aCount = PaymentPreferencesUtil.getPaymentAppUseCount(a.getIdentifier());
int bCount = PaymentPreferencesUtil.getPaymentAppUseCount(b.getIdentifier());
long aDate = PaymentPreferencesUtil.getPaymentAppLastUseDate(a.getIdentifier());
long bDate = PaymentPreferencesUtil.getPaymentAppLastUseDate(a.getIdentifier());

return Double.compare(getFrecencyScore(aCount, aDate), getFrecencyScore(bCount, bDate));
}

/**
* Compares two Completable by completeness score.
* Return negative value if a has strictly lower completeness score than b.
* Return zero if a and b have the same completeness score.
* Return positive value if a has strictly higher completeness score than b.
*/
private static int compareCompletablesByCompleteness(Completable a, Completable b) {
return Integer.compare(a.getCompletenessScore(), b.getCompletenessScore());
}

/**
* The frecency score is calculated according to use count and last use date. The formula is
* the same as the one used in GetFrecencyScore in autofill_data_model.cc.
*/
private static final double getFrecencyScore(int count, long date) {
long currentTime = System.currentTimeMillis();
return -Math.log((currentTime - date) / (24 * 60 * 60 * 1000) + 2) / Math.log(count + 2);
}

@Nullable
private PaymentRequestClient getClient() {
return mComponentPaymentRequestImpl.getClient();
Expand Down

0 comments on commit 0abb2e3

Please sign in to comment.