Added new API to set field classification algorithms per category.

Originally one field classification algorithm was used to classify every
field. The change allows different category of fields to be classified
by different field classification algorithms.

Change-Id: I27205a4096774d6e0c0d56da5e0fd38dda995d8f
Fixes: 118681526
Test: atest CtsAutoFillServiceTestCases
Test: atest android.autofillservice.cts.FieldsClassificationTest
This commit is contained in:
Adam He
2018-11-06 14:03:26 -08:00
parent e02414c225
commit 71d53e0cc8
18 changed files with 615 additions and 179 deletions

View File

@@ -40506,6 +40506,7 @@ package android.service.autofill {
public final class UserData implements android.os.Parcelable {
method public int describeContents();
method public java.lang.String getFieldClassificationAlgorithm();
method public java.lang.String getFieldClassificationAlgorithmForCategory(java.lang.String);
method public java.lang.String getId();
method public static int getMaxCategoryCount();
method public static int getMaxFieldClassificationIdsSize();
@@ -40521,6 +40522,7 @@ package android.service.autofill {
method public android.service.autofill.UserData.Builder add(java.lang.String, java.lang.String);
method public android.service.autofill.UserData build();
method public android.service.autofill.UserData.Builder setFieldClassificationAlgorithm(java.lang.String, android.os.Bundle);
method public android.service.autofill.UserData.Builder setFieldClassificationAlgorithmForCategory(java.lang.String, java.lang.String, android.os.Bundle);
}
public abstract interface Validator {

View File

@@ -4871,7 +4871,10 @@ package android.service.autofill {
public abstract class AutofillFieldClassificationService extends android.app.Service {
method public android.os.IBinder onBind(android.content.Intent);
method public float[][] onGetScores(java.lang.String, android.os.Bundle, java.util.List<android.view.autofill.AutofillValue>, java.util.List<java.lang.String>);
method public float[][] onCalculateScores(java.util.List<android.view.autofill.AutofillValue>, java.util.List<java.lang.String>, java.util.List<java.lang.String>, java.lang.String, android.os.Bundle, java.util.Map, java.util.Map);
method public deprecated float[][] onGetScores(java.lang.String, android.os.Bundle, java.util.List<android.view.autofill.AutofillValue>, java.util.List<java.lang.String>);
field public static final java.lang.String REQUIRED_ALGORITHM_EDIT_DISTANCE = "EDIT_DISTANCE";
field public static final java.lang.String REQUIRED_ALGORITHM_EXACT_MATCH = "EXACT_MATCH";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillFieldClassificationService";
field public static final java.lang.String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS = "android.autofill.field_classification.available_algorithms";
field public static final java.lang.String SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM = "android.autofill.field_classification.default_algorithm";

View File

@@ -1066,6 +1066,15 @@ package android.security.keystore {
package android.service.autofill {
public abstract class AutofillFieldClassificationService extends android.app.Service {
method public android.os.IBinder onBind(android.content.Intent);
field public static final java.lang.String REQUIRED_ALGORITHM_EDIT_DISTANCE = "EDIT_DISTANCE";
field public static final java.lang.String REQUIRED_ALGORITHM_EXACT_MATCH = "EXACT_MATCH";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillFieldClassificationService";
field public static final java.lang.String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS = "android.autofill.field_classification.available_algorithms";
field public static final java.lang.String SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM = "android.autofill.field_classification.default_algorithm";
}
public final class CharSequenceTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation {
method public void apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int) throws java.lang.Exception;
}
@@ -1122,6 +1131,10 @@ package android.service.autofill {
method public android.view.autofill.AutofillValue sanitize(android.view.autofill.AutofillValue);
}
public final class UserData implements android.os.Parcelable {
method public android.util.ArrayMap<java.lang.String, java.lang.String> getFieldClassificationAlgorithms();
}
public abstract interface ValueFinder {
method public default java.lang.String findByAutofillId(android.view.autofill.AutofillId);
method public abstract android.view.autofill.AutofillValue findRawValueByAutofillId(android.view.autofill.AutofillId);

View File

@@ -20,6 +20,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
@@ -35,6 +36,7 @@ import android.view.autofill.AutofillValue;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* A service that calculates field classification scores.
@@ -51,6 +53,7 @@ import java.util.List;
* {@hide}
*/
@SystemApi
@TestApi
public abstract class AutofillFieldClassificationService extends Service {
private static final String TAG = "AutofillFieldClassificationService";
@@ -75,17 +78,32 @@ public abstract class AutofillFieldClassificationService extends Service {
public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
"android.autofill.field_classification.available_algorithms";
/**
* Field classification algorithm that computes the edit distance between two Strings.
*
* <p>Service implementation must provide this algorithm.</p>
*/
public static final String REQUIRED_ALGORITHM_EDIT_DISTANCE = "EDIT_DISTANCE";
/**
* Field classification algorithm that computes whether the last four digits between two
* Strings match exactly.
*
* <p>Service implementation must provide this algorithm.</p>
*/
public static final String REQUIRED_ALGORITHM_EXACT_MATCH = "EXACT_MATCH";
/** {@hide} **/
public static final String EXTRA_SCORES = "scores";
private AutofillFieldClassificationServiceWrapper mWrapper;
private void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs,
List<AutofillValue> actualValues, String[] userDataValues) {
private void calculateScores(RemoteCallback callback, List<AutofillValue> actualValues,
String[] userDataValues, String[] categoryIds, String defaultAlgorithm,
Bundle defaultArgs, Map algorithms, Map args) {
final Bundle data = new Bundle();
final float[][] scores = onGetScores(algorithmName, algorithmArgs, actualValues,
Arrays.asList(userDataValues));
final float[][] scores = onCalculateScores(actualValues, Arrays.asList(userDataValues),
Arrays.asList(categoryIds), defaultAlgorithm, defaultArgs, algorithms, args);
if (scores != null) {
data.putParcelable(EXTRA_SCORES, new Scores(scores));
}
@@ -169,9 +187,12 @@ public abstract class AutofillFieldClassificationService extends Service {
* @return the calculated scores of {@code actualValues} x {@code userDataValues}.
*
* {@hide}
*
* @deprecated Use {@link AutofillFieldClassificationService#onCalculateScores} instead.
*/
@Nullable
@SystemApi
@Deprecated
public float[][] onGetScores(@Nullable String algorithm,
@Nullable Bundle algorithmOptions, @NonNull List<AutofillValue> actualValues,
@NonNull List<String> userDataValues) {
@@ -179,16 +200,98 @@ public abstract class AutofillFieldClassificationService extends Service {
return null;
}
/**
* Calculates field classification scores in a batch.
*
* <p>A field classification score is a {@code float} representing how well an
* {@link AutofillValue} matches a expected value predicted by an autofill service
* &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
*
* <p>The exact score depends on the algorithm used to calculate it&mdash;the service must
* provide at least one default algorithm (which is used when the algorithm is not specified
* or is invalid), but it could provide more (in which case the algorithm name should be
* specified by the caller when calculating the scores).
*
* <p>For example, if the service provides an algorithm named {@code EXACT_MATCH} that
* returns {@code 1.0} if all characters match or {@code 0.0} otherwise, a call to:
*
* <pre>
* HashMap algorithms = new HashMap<>();
* algorithms.put("email", "EXACT_MATCH");
* algorithms.put("phone", "EXACT_MATCH");
*
* HashMap args = new HashMap<>();
* args.put("email", null);
* args.put("phone", null);
*
* service.onCalculateScores(Arrays.asList(AutofillValue.forText("email1"),
* AutofillValue.forText("PHONE1")), Arrays.asList("email1", "phone1"),
* Array.asList("email", "phone"), algorithms, args);
* </pre>
*
* <p>Returns:
*
* <pre>
* [
* [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
* [0.0, 0.0] // "PHONE1" compared against ["email1", "phone1"]
* ];
* </pre>
*
* <p>If the same algorithm allows the caller to specify whether the comparisons should be
* case sensitive by passing a boolean option named {@code "case_sensitive"}, then a call to:
*
* <pre>
* Bundle algorithmOptions = new Bundle();
* algorithmOptions.putBoolean("case_sensitive", false);
* args.put("phone", algorithmOptions);
*
* service.onCalculateScores(Arrays.asList(AutofillValue.forText("email1"),
* AutofillValue.forText("PHONE1")), Arrays.asList("email1", "phone1"),
* Array.asList("email", "phone"), algorithms, args);
* </pre>
*
* <p>Returns:
*
* <pre>
* [
* [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
* [0.0, 1.0] // "PHONE1" compared against ["email1", "phone1"]
* ];
* </pre>
*
* @param actualValues values entered by the user.
* @param userDataValues values predicted from the user data.
* @param categoryIds category Ids correspoinding to userDataValues
* @param defaultAlgorithm default field classification algorithm
* @param algorithms array of field classification algorithms
* @return the calculated scores of {@code actualValues} x {@code userDataValues}.
*
* {@hide}
*/
@Nullable
@SystemApi
public float[][] onCalculateScores(@NonNull List<AutofillValue> actualValues,
@NonNull List<String> userDataValues, @NonNull List<String> categoryIds,
@Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs,
@Nullable Map algorithms, @Nullable Map args) {
Log.e(TAG, "service implementation (" + getClass()
+ " does not implement onCalculateScore()");
return null;
}
private final class AutofillFieldClassificationServiceWrapper
extends IAutofillFieldClassificationService.Stub {
@Override
public void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs,
List<AutofillValue> actualValues, String[] userDataValues)
throws RemoteException {
public void calculateScores(RemoteCallback callback, List<AutofillValue> actualValues,
String[] userDataValues, String[] categoryIds, String defaultAlgorithm,
Bundle defaultArgs, Map algorithms, Map args)
throws RemoteException {
mHandler.sendMessage(obtainMessage(
AutofillFieldClassificationService::getScores,
AutofillFieldClassificationService::calculateScores,
AutofillFieldClassificationService.this,
callback, algorithmName, algorithmArgs, actualValues, userDataValues));
callback, actualValues, userDataValues, categoryIds, defaultAlgorithm,
defaultArgs, algorithms, args));
}
}

View File

@@ -20,6 +20,7 @@ import android.os.Bundle;
import android.os.RemoteCallback;
import android.view.autofill.AutofillValue;
import java.util.List;
import java.util.Map;
/**
* Service used to calculate match scores for Autofill Field Classification.
@@ -27,6 +28,8 @@ import java.util.List;
* @hide
*/
oneway interface IAutofillFieldClassificationService {
void getScores(in RemoteCallback callback, String algorithmName, in Bundle algorithmArgs,
in List<AutofillValue> actualValues, in String[] userDataValues);
void calculateScores(in RemoteCallback callback, in List<AutofillValue> actualValues,
in String[] userDataValues, in String[] categoryIds,
in String defaultAlgorithm, in Bundle defaultArgs,
in Map algorithms, in Map args);
}

View File

@@ -24,6 +24,7 @@ import static android.view.autofill.Helper.sDebug;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.ActivityThread;
import android.content.ContentResolver;
import android.os.Bundle;
@@ -32,6 +33,7 @@ import android.os.Parcelable;
import android.provider.Settings;
import android.service.autofill.FieldClassification.Match;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.view.autofill.AutofillManager;
@@ -57,28 +59,57 @@ public final class UserData implements Parcelable {
private static final int DEFAULT_MAX_VALUE_LENGTH = 100;
private final String mId;
private final String mAlgorithm;
private final Bundle mAlgorithmArgs;
private final String[] mCategoryIds;
private final String[] mValues;
private final String mDefaultAlgorithm;
private final Bundle mDefaultArgs;
private final ArrayMap<String, String> mCategoryAlgorithms;
private final ArrayMap<String, Bundle> mCategoryArgs;
private UserData(Builder builder) {
mId = builder.mId;
mAlgorithm = builder.mAlgorithm;
mAlgorithmArgs = builder.mAlgorithmArgs;
mCategoryIds = new String[builder.mCategoryIds.size()];
builder.mCategoryIds.toArray(mCategoryIds);
mValues = new String[builder.mValues.size()];
builder.mValues.toArray(mValues);
builder.mValues.toArray(mValues);
mDefaultAlgorithm = builder.mDefaultAlgorithm;
mDefaultArgs = builder.mDefaultArgs;
mCategoryAlgorithms = builder.mCategoryAlgorithms;
mCategoryArgs = builder.mCategoryArgs;
}
/**
* Gets the name of the algorithm that is used to calculate
* {@link Match#getScore() match scores}.
* Gets the name of the default algorithm that is used to calculate
* {@link Match#getScore()} match scores}.
*/
@Nullable
public String getFieldClassificationAlgorithm() {
return mAlgorithm;
return mDefaultAlgorithm;
}
/** @hide */
public Bundle getDefaultFieldClassificationArgs() {
return mDefaultArgs;
}
/**
* Gets the name of the algorithm corresponding to the specific autofill category
* that is used to calculate {@link Match#getScore() match scores}
*
* @param categoryId autofill field category
*
* @return String name of algorithm, null if none found.
*/
@Nullable
public String getFieldClassificationAlgorithmForCategory(@NonNull String categoryId) {
Preconditions.checkNotNull(categoryId);
if (mCategoryAlgorithms == null || !mCategoryAlgorithms.containsKey(categoryId)) {
return null;
}
return mCategoryAlgorithms.get(categoryId);
}
/**
@@ -88,11 +119,6 @@ public final class UserData implements Parcelable {
return mId;
}
/** @hide */
public Bundle getAlgorithmArgs() {
return mAlgorithmArgs;
}
/** @hide */
public String[] getCategoryIds() {
return mCategoryIds;
@@ -103,12 +129,30 @@ public final class UserData implements Parcelable {
return mValues;
}
/** @hide */
@TestApi
public ArrayMap<String, String> getFieldClassificationAlgorithms() {
return mCategoryAlgorithms;
}
/** @hide */
public ArrayMap<String, Bundle> getFieldClassificationArgs() {
return mCategoryArgs;
}
/** @hide */
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("id: "); pw.print(mId);
pw.print(prefix); pw.print("Algorithm: "); pw.print(mAlgorithm);
pw.print(" Args: "); pw.println(mAlgorithmArgs);
pw.print(prefix); pw.print("Default Algorithm: "); pw.print(mDefaultAlgorithm);
pw.print(prefix); pw.print("Default Args"); pw.print(mDefaultArgs);
if (mCategoryAlgorithms != null && mCategoryAlgorithms.size() > 0) {
pw.print(prefix); pw.print("Algorithms per category: ");
for (int i = 0; i < mCategoryAlgorithms.size(); i++) {
pw.print(prefix); pw.print(prefix); pw.print(mCategoryAlgorithms.keyAt(i));
pw.print(": "); pw.println(Helper.getRedacted(mCategoryAlgorithms.valueAt(i)));
pw.print("args="); pw.print(mCategoryArgs.get(mCategoryAlgorithms.keyAt(i)));
}
}
// Cannot disclose field ids or values because they could contain PII
pw.print(prefix); pw.print("Field ids size: "); pw.println(mCategoryIds.length);
for (int i = 0; i < mCategoryIds.length; i++) {
@@ -139,8 +183,13 @@ public final class UserData implements Parcelable {
private final String mId;
private final ArrayList<String> mCategoryIds;
private final ArrayList<String> mValues;
private String mAlgorithm;
private Bundle mAlgorithmArgs;
private String mDefaultAlgorithm;
private Bundle mDefaultArgs;
// Map of autofill field categories to fleid classification algorithms and args
private ArrayMap<String, String> mCategoryAlgorithms;
private ArrayMap<String, Bundle> mCategoryArgs;
private boolean mDestroyed;
// Non-persistent array used to limit the number of unique ids.
@@ -148,7 +197,6 @@ public final class UserData implements Parcelable {
// Non-persistent array used to ignore duplaicated value/category pairs.
private final ArraySet<String> mUniqueValueCategoryPairs;
/**
* Creates a new builder for the user data used for <a href="#FieldClassification">field
* classification</a>.
@@ -169,7 +217,7 @@ public final class UserData implements Parcelable {
* {@link AutofillManager#getUserData()}).
*
* @param value value of the user data.
* @param categoryId string used to identify the category the value is associated with.
* @param categoryId autofill field category.
*
* @throws IllegalArgumentException if any of the following occurs:
* <ul>
@@ -189,13 +237,15 @@ public final class UserData implements Parcelable {
mCategoryIds = new ArrayList<>(maxUserDataSize);
mValues = new ArrayList<>(maxUserDataSize);
mUniqueValueCategoryPairs = new ArraySet<>(maxUserDataSize);
mUniqueCategoryIds = new ArraySet<>(getMaxCategoryCount());
addMapping(value, categoryId);
}
/**
* Sets the algorithm used for <a href="#FieldClassification">field classification</a>.
* Sets the default algorithm used for
* <a href="#FieldClassification">field classification</a>.
*
* <p>The currently available algorithms can be retrieve through
* {@link AutofillManager#getAvailableFieldClassificationAlgorithms()}.
@@ -212,8 +262,40 @@ public final class UserData implements Parcelable {
public Builder setFieldClassificationAlgorithm(@Nullable String name,
@Nullable Bundle args) {
throwIfDestroyed();
mAlgorithm = name;
mAlgorithmArgs = args;
mDefaultAlgorithm = name;
mDefaultArgs = args;
return this;
}
/**
* Sets the algorithm used for <a href="#FieldClassification">field classification</a>
* for the specified category.
*
* <p>The currently available algorithms can be retrieved through
* {@link AutofillManager#getAvailableFieldClassificationAlgorithms()}.
*
* <p>If not set, the
* {@link AutofillManager#getDefaultFieldClassificationAlgorithm() default algorithm} is
* used instead.
*
* @param categoryId autofill field category.
* @param name name of the algorithm or {@code null} to used default.
* @param args optional arguments to the algorithm.
*
* @return this builder
*/
public Builder setFieldClassificationAlgorithmForCategory(@NonNull String categoryId,
@Nullable String name, @Nullable Bundle args) {
throwIfDestroyed();
Preconditions.checkNotNull(categoryId);
if (mCategoryAlgorithms == null) {
mCategoryAlgorithms = new ArrayMap<>(getMaxCategoryCount());
}
if (mCategoryArgs == null) {
mCategoryArgs = new ArrayMap<>(getMaxCategoryCount());
}
mCategoryAlgorithms.put(categoryId, name);
mCategoryArgs.put(categoryId, args);
return this;
}
@@ -317,8 +399,7 @@ public final class UserData implements Parcelable {
public String toString() {
if (!sDebug) return super.toString();
final StringBuilder builder = new StringBuilder("UserData: [id=").append(mId)
.append(", algorithm=").append(mAlgorithm);
final StringBuilder builder = new StringBuilder("UserData: [id=").append(mId);
// Cannot disclose category ids or values because they could contain PII
builder.append(", categoryIds=");
Helper.appendRedacted(builder, mCategoryIds);
@@ -341,8 +422,10 @@ public final class UserData implements Parcelable {
parcel.writeString(mId);
parcel.writeStringArray(mCategoryIds);
parcel.writeStringArray(mValues);
parcel.writeString(mAlgorithm);
parcel.writeBundle(mAlgorithmArgs);
parcel.writeString(mDefaultAlgorithm);
parcel.writeBundle(mDefaultArgs);
parcel.writeMap(mCategoryAlgorithms);
parcel.writeMap(mCategoryArgs);
}
public static final Parcelable.Creator<UserData> CREATOR =
@@ -355,10 +438,28 @@ public final class UserData implements Parcelable {
final String id = parcel.readString();
final String[] categoryIds = parcel.readStringArray();
final String[] values = parcel.readStringArray();
final String defaultAlgorithm = parcel.readString();
final Bundle defaultArgs = parcel.readBundle();
final ArrayMap<String, String> categoryAlgorithms = new ArrayMap<>();
parcel.readMap(categoryAlgorithms, String.class.getClassLoader());
final ArrayMap<String, Bundle> categoryArgs = new ArrayMap<>();
parcel.readMap(categoryArgs, Bundle.class.getClassLoader());
final Builder builder = new Builder(id, values[0], categoryIds[0])
.setFieldClassificationAlgorithm(parcel.readString(), parcel.readBundle());
.setFieldClassificationAlgorithm(defaultAlgorithm, defaultArgs);
for (int i = 1; i < categoryIds.length; i++) {
builder.add(values[i], categoryIds[i]);
String categoryId = categoryIds[i];
builder.add(values[i], categoryId);
}
final int size = categoryAlgorithms.size();
if (size > 0) {
for (int i = 0; i < size; i++) {
final String categoryId = categoryAlgorithms.keyAt(i);
builder.setFieldClassificationAlgorithmForCategory(categoryId,
categoryAlgorithms.valueAt(i), categoryArgs.get(categoryId));
}
}
return builder.build();
}

View File

@@ -22,5 +22,6 @@
<string name="autofill_field_classification_default_algorithm">EDIT_DISTANCE</string>
<string-array name="autofill_field_classification_available_algorithms">
<item>EDIT_DISTANCE</item>
<item>EXACT_MATCH</item>
</string-array>
</resources>

View File

@@ -15,8 +15,6 @@
*/
package android.ext.services.autofill;
import static android.ext.services.autofill.EditDistanceScorer.DEFAULT_ALGORITHM;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
@@ -26,27 +24,72 @@ import android.view.autofill.AutofillValue;
import com.android.internal.util.ArrayUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AutofillFieldClassificationServiceImpl extends AutofillFieldClassificationService {
private static final String TAG = "AutofillFieldClassificationServiceImpl";
private static final String DEFAULT_ALGORITHM = REQUIRED_ALGORITHM_EDIT_DISTANCE;
@Nullable
@Override
public float[][] onGetScores(@Nullable String algorithmName,
@Nullable Bundle algorithmArgs, @NonNull List<AutofillValue> actualValues,
@NonNull List<String> userDataValues) {
/** @hide */
public float[][] onCalculateScores(@NonNull List<AutofillValue> actualValues,
@NonNull List<String> userDataValues, @NonNull List<String> categoryIds,
@Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs,
@Nullable Map algorithms, @Nullable Map args) {
if (ArrayUtils.isEmpty(actualValues) || ArrayUtils.isEmpty(userDataValues)) {
Log.w(TAG, "getScores(): empty currentvalues (" + actualValues + ") or userValues ("
+ userDataValues + ")");
Log.w(TAG, "calculateScores(): empty currentvalues (" + actualValues
+ ") or userValues (" + userDataValues + ")");
return null;
}
if (algorithmName != null && !algorithmName.equals(DEFAULT_ALGORITHM)) {
Log.w(TAG, "Ignoring invalid algorithm (" + algorithmName + ") and using "
+ DEFAULT_ALGORITHM + " instead");
}
return EditDistanceScorer.getScores(actualValues, userDataValues);
return calculateScores(actualValues, userDataValues, categoryIds, defaultAlgorithm,
defaultArgs, (HashMap<String, String>) algorithms,
(HashMap<String, Bundle>) args);
}
/** @hide */
public float[][] calculateScores(@NonNull List<AutofillValue> actualValues,
@NonNull List<String> userDataValues, @NonNull List<String> categoryIds,
@Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs,
@Nullable HashMap<String, String> algorithms,
@Nullable HashMap<String, Bundle> args) {
final int actualValuesSize = actualValues.size();
final int userDataValuesSize = userDataValues.size();
final float[][] scores = new float[actualValuesSize][userDataValuesSize];
for (int j = 0; j < userDataValuesSize; j++) {
final String categoryId = categoryIds.get(j);
String algorithmName = defaultAlgorithm;
Bundle arg = defaultArgs;
if (algorithms != null && algorithms.containsKey(categoryId)) {
algorithmName = algorithms.get(categoryId);
}
if (args != null && args.containsKey(categoryId)) {
arg = args.get(categoryId);
}
if (algorithmName == null || (!algorithmName.equals(DEFAULT_ALGORITHM)
&& !algorithmName.equals(REQUIRED_ALGORITHM_EXACT_MATCH))) {
Log.w(TAG, "algorithmName is " + algorithmName + ", defaulting to "
+ DEFAULT_ALGORITHM);
algorithmName = DEFAULT_ALGORITHM;
}
for (int i = 0; i < actualValuesSize; i++) {
if (algorithmName.equals(DEFAULT_ALGORITHM)) {
scores[i][j] = EditDistanceScorer.calculateScore(actualValues.get(i),
userDataValues.get(j));
} else {
scores[i][j] = ExactMatch.calculateScore(actualValues.get(i),
userDataValues.get(j), arg);
}
}
}
return scores;
}
}

View File

@@ -17,13 +17,10 @@ package android.ext.services.autofill;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Log;
import android.view.autofill.AutofillValue;
import com.android.internal.annotations.VisibleForTesting;
import java.util.List;
final class EditDistanceScorer {
private static final String TAG = "EditDistanceScorer";
@@ -31,15 +28,14 @@ final class EditDistanceScorer {
// TODO(b/70291841): STOPSHIP - set to false before launching
private static final boolean DEBUG = true;
static final String DEFAULT_ALGORITHM = "EDIT_DISTANCE";
/**
* Gets the field classification score of 2 values based on the edit distance between them.
*
* <p>The score is defined as: @(max_length - edit_distance) / max_length
*/
@VisibleForTesting
static float getScore(@Nullable AutofillValue actualValue, @Nullable String userDataValue) {
static float calculateScore(@Nullable AutofillValue actualValue,
@Nullable String userDataValue) {
if (actualValue == null || !actualValue.isText() || userDataValue == null) return 0;
final String actualValueText = actualValue.getTextValue().toString();
@@ -123,26 +119,5 @@ final class EditDistanceScorer {
return d[m][n];
}
/**
* Gets the scores in a batch.
*/
static float[][] getScores(@NonNull List<AutofillValue> actualValues,
@NonNull List<String> userDataValues) {
final int actualValuesSize = actualValues.size();
final int userDataValuesSize = userDataValues.size();
if (DEBUG) {
Log.d(TAG, "getScores() will return a " + actualValuesSize + "x"
+ userDataValuesSize + " matrix for " + DEFAULT_ALGORITHM);
}
final float[][] scores = new float[actualValuesSize][userDataValuesSize];
for (int i = 0; i < actualValuesSize; i++) {
for (int j = 0; j < userDataValuesSize; j++) {
final float score = getScore(actualValues.get(i), userDataValues.get(j));
scores[i][j] = score;
}
}
return scores;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.ext.services.autofill;
import android.annotation.Nullable;
import android.os.Bundle;
import android.view.autofill.AutofillValue;
import com.android.internal.annotations.VisibleForTesting;
final class ExactMatch {
/**
* Gets the field classification score of 2 values based on whether they are an exact match
*
* @return {@code 1.0} if the two values are an exact match, {@code 0.0} otherwise.
*/
@VisibleForTesting
static float calculateScore(@Nullable AutofillValue actualValue,
@Nullable String userDataValue, @Nullable Bundle args) {
if (actualValue == null || !actualValue.isText() || userDataValue == null) return 0;
final String actualValueText = actualValue.getTextValue().toString();
final int suffixLength;
if (args != null) {
suffixLength = args.getInt("suffix", -1);
if (suffixLength < 0) {
throw new IllegalArgumentException("suffix argument is invalid");
}
final String actualValueSuffix;
if (suffixLength < actualValueText.length()) {
actualValueSuffix = actualValueText.substring(actualValueText.length()
- suffixLength);
} else {
actualValueSuffix = actualValueText;
}
final String userDataValueSuffix;
if (suffixLength < userDataValue.length()) {
userDataValueSuffix = userDataValue.substring(userDataValue.length()
- suffixLength);
} else {
userDataValueSuffix = userDataValue;
}
return (actualValueSuffix.equalsIgnoreCase(userDataValueSuffix)) ? 1 : 0;
} else {
return actualValueText.equalsIgnoreCase(userDataValue) ? 1 : 0;
}
}
}

View File

@@ -12,7 +12,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
mockito-target-minus-junit4 \
espresso-core \
truth-prebuilt \
testables
testables \
testng
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)

View File

@@ -16,14 +16,21 @@
package android.ext.services.autofill;
import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EXACT_MATCH;
import static android.view.autofill.AutofillValue.forText;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import android.os.Bundle;
import android.view.autofill.AutofillValue;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import static com.google.common.truth.Truth.assertThat;
import android.view.autofill.AutofillValue;
import java.util.HashMap;
import java.util.List;
/**
* Contains the base tests that does not rely on the specific algorithm implementation.
@@ -34,26 +41,78 @@ public class AutofillFieldClassificationServiceImplTest {
new AutofillFieldClassificationServiceImpl();
@Test
public void testOnGetScores_nullActualValues() {
assertThat(mService.onGetScores(null, null, null, Arrays.asList("whatever"))).isNull();
public void testOnCalculateScores_nullActualValues() {
assertThat(mService.onCalculateScores(null, null, null, null, null, null, null)).isNull();
}
@Test
public void testOnGetScores_emptyActualValues() {
assertThat(mService.onGetScores(null, null, Collections.emptyList(),
Arrays.asList("whatever"))).isNull();
public void testOnCalculateScores_emptyActualValues() {
assertThat(mService.onCalculateScores(Collections.emptyList(), Arrays.asList("whatever"),
null, null, null, null, null)).isNull();
}
@Test
public void testOnGetScores_nullUserDataValues() {
assertThat(mService.onGetScores(null, null,
Arrays.asList(AutofillValue.forText("whatever")), null)).isNull();
public void testOnCalculateScores_nullUserDataValues() {
assertThat(mService.onCalculateScores(Arrays.asList(AutofillValue.forText("whatever")),
null, null, null, null, null, null)).isNull();
}
@Test
public void testOnGetScores_emptyUserDataValues() {
assertThat(mService.onGetScores(null, null,
Arrays.asList(AutofillValue.forText("whatever")), Collections.emptyList()))
.isNull();
public void testOnCalculateScores_emptyUserDataValues() {
assertThat(mService.onCalculateScores(Arrays.asList(AutofillValue.forText("whatever")),
Collections.emptyList(), null, null, null, null, null))
.isNull();
}
@Test
public void testCalculateScores() {
final List<AutofillValue> actualValues = Arrays.asList(forText("A"), forText("b"),
forText("dude"));
final List<String> userDataValues = Arrays.asList("a", "b", "B", "ab", "c", "dude",
"sweet_dude", "dude_sweet");
final List<String> categoryIds = Arrays.asList("cat", "cat", "cat", "cat", "cat", "last4",
"last4", "last4");
final HashMap<String, String> algorithms = new HashMap<>(1);
algorithms.put("last4", REQUIRED_ALGORITHM_EXACT_MATCH);
final Bundle last4Bundle = new Bundle();
last4Bundle.putInt("suffix", 4);
final HashMap<String, Bundle> args = new HashMap<>(1);
args.put("last4", last4Bundle);
final float[][] expectedScores = new float[][] {
new float[] { 1F, 0F, 0F, 0.5F, 0F, 0F, 0F, 0F },
new float[] { 0F, 1F, 1F, 0.5F, 0F, 0F, 0F, 0F },
new float[] { 0F, 0F, 0F, 0F , 0F, 1F, 1F, 0F }
};
final float[][] actualScores = mService.onCalculateScores(actualValues, userDataValues,
categoryIds, null, null, algorithms, args);
// Unfortunately, Truth does not have an easy way to compare float matrices and show useful
// messages in case of error, so we need to check.
assertWithMessage("actual=%s, expected=%s", toString(actualScores),
toString(expectedScores)).that(actualScores.length).isEqualTo(3);
for (int i = 0; i < 3; i++) {
assertWithMessage("actual=%s, expected=%s", toString(actualScores),
toString(expectedScores)).that(actualScores[i].length).isEqualTo(8);
}
for (int i = 0; i < actualScores.length; i++) {
final float[] line = actualScores[i];
for (int j = 0; j < line.length; j++) {
float cell = line[j];
assertWithMessage("wrong score at [%s, %s]", i, j).that(cell).isWithin(0.01F)
.of(expectedScores[i][j]);
}
}
}
public static String toString(float[][] matrix) {
final StringBuilder string = new StringBuilder("[ ");
for (int i = 0; i < matrix.length; i++) {
string.append(Arrays.toString(matrix[i])).append(" ");
}
return string.append(" ]").toString();
}
}

View File

@@ -15,107 +15,67 @@
*/
package android.ext.services.autofill;
import static android.ext.services.autofill.EditDistanceScorer.getScore;
import static android.ext.services.autofill.EditDistanceScorer.getScores;
import static android.view.autofill.AutofillValue.forText;
import static android.ext.services.autofill.EditDistanceScorer.calculateScore;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import android.view.autofill.AutofillValue;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
public class EditDistanceScorerTest {
@Test
public void testGetScore_nullValue() {
assertFloat(getScore(null, "D'OH!"), 0);
public void testCalculateScore_nullValue() {
assertFloat(calculateScore(null, "D'OH!"), 0);
}
@Test
public void testGetScore_nonTextValue() {
assertFloat(getScore(AutofillValue.forToggle(true), "D'OH!"), 0);
public void testCalculateScore_nonTextValue() {
assertFloat(calculateScore(AutofillValue.forToggle(true), "D'OH!"), 0);
}
@Test
public void testGetScore_nullUserData() {
assertFloat(getScore(AutofillValue.forText("D'OH!"), null), 0);
public void testCalculateScore_nullUserData() {
assertFloat(calculateScore(AutofillValue.forText("D'OH!"), null), 0);
}
@Test
public void testGetScore_fullMatch() {
assertFloat(getScore(AutofillValue.forText("D'OH!"), "D'OH!"), 1);
assertFloat(getScore(AutofillValue.forText(""), ""), 1);
public void testCalculateScore_fullMatch() {
assertFloat(calculateScore(AutofillValue.forText("D'OH!"), "D'OH!"), 1);
assertFloat(calculateScore(AutofillValue.forText(""), ""), 1);
}
@Test
public void testGetScore_fullMatchMixedCase() {
assertFloat(getScore(AutofillValue.forText("D'OH!"), "D'oH!"), 1);
public void testCalculateScore_fullMatchMixedCase() {
assertFloat(calculateScore(AutofillValue.forText("D'OH!"), "D'oH!"), 1);
}
@Test
public void testGetScore_mismatchDifferentSizes() {
assertFloat(getScore(AutofillValue.forText("X"), "Xy"), 0.50F);
assertFloat(getScore(AutofillValue.forText("Xy"), "X"), 0.50F);
assertFloat(getScore(AutofillValue.forText("One"), "MoreThanOne"), 0.27F);
assertFloat(getScore(AutofillValue.forText("MoreThanOne"), "One"), 0.27F);
assertFloat(getScore(AutofillValue.forText("1600 Amphitheatre Parkway"),
public void testCalculateScore_mismatchDifferentSizes() {
assertFloat(calculateScore(AutofillValue.forText("X"), "Xy"), 0.50F);
assertFloat(calculateScore(AutofillValue.forText("Xy"), "X"), 0.50F);
assertFloat(calculateScore(AutofillValue.forText("One"), "MoreThanOne"), 0.27F);
assertFloat(calculateScore(AutofillValue.forText("MoreThanOne"), "One"), 0.27F);
assertFloat(calculateScore(AutofillValue.forText("1600 Amphitheatre Parkway"),
"1600 Amphitheatre Pkwy"), 0.88F);
assertFloat(getScore(AutofillValue.forText("1600 Amphitheatre Pkwy"),
assertFloat(calculateScore(AutofillValue.forText("1600 Amphitheatre Pkwy"),
"1600 Amphitheatre Parkway"), 0.88F);
}
@Test
public void testGetScore_partialMatch() {
assertFloat(getScore(AutofillValue.forText("Dude"), "Dxxx"), 0.25F);
assertFloat(getScore(AutofillValue.forText("Dude"), "DUxx"), 0.50F);
assertFloat(getScore(AutofillValue.forText("Dude"), "DUDx"), 0.75F);
assertFloat(getScore(AutofillValue.forText("Dxxx"), "Dude"), 0.25F);
assertFloat(getScore(AutofillValue.forText("DUxx"), "Dude"), 0.50F);
assertFloat(getScore(AutofillValue.forText("DUDx"), "Dude"), 0.75F);
}
@Test
public void testGetScores() {
final List<AutofillValue> actualValues = Arrays.asList(forText("A"), forText("b"));
final List<String> userDataValues = Arrays.asList("a", "B", "ab", "c");
final float[][] expectedScores = new float[][] {
new float[] { 1F, 0F, 0.5F, 0F },
new float[] { 0F, 1F, 0.5F, 0F }
};
final float[][] actualScores = getScores(actualValues, userDataValues);
// Unfortunately, Truth does not have an easy way to compare float matrices and show useful
// messages in case of error, so we need to check.
assertWithMessage("actual=%s, expected=%s", toString(actualScores),
toString(expectedScores)).that(actualScores.length).isEqualTo(2);
assertWithMessage("actual=%s, expected=%s", toString(actualScores),
toString(expectedScores)).that(actualScores[0].length).isEqualTo(4);
assertWithMessage("actual=%s, expected=%s", toString(actualScores),
toString(expectedScores)).that(actualScores[1].length).isEqualTo(4);
for (int i = 0; i < actualScores.length; i++) {
final float[] line = actualScores[i];
for (int j = 0; j < line.length; j++) {
float cell = line[j];
assertWithMessage("wrong score at [%s, %s]", i, j).that(cell).isWithin(0.01F)
.of(expectedScores[i][j]);
}
}
public void testCalculateScore_partialMatch() {
assertFloat(calculateScore(AutofillValue.forText("Dude"), "Dxxx"), 0.25F);
assertFloat(calculateScore(AutofillValue.forText("Dude"), "DUxx"), 0.50F);
assertFloat(calculateScore(AutofillValue.forText("Dude"), "DUDx"), 0.75F);
assertFloat(calculateScore(AutofillValue.forText("Dxxx"), "Dude"), 0.25F);
assertFloat(calculateScore(AutofillValue.forText("DUxx"), "Dude"), 0.50F);
assertFloat(calculateScore(AutofillValue.forText("DUDx"), "Dude"), 0.75F);
}
public static void assertFloat(float actualValue, float expectedValue) {
assertThat(actualValue).isWithin(0.01F).of(expectedValue);
}
public static String toString(float[][] matrix) {
final StringBuilder string = new StringBuilder("[ ");
for (int i = 0; i < matrix.length; i++) {
string.append(Arrays.toString(matrix[i])).append(" ");
}
return string.append(" ]").toString();
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.ext.services.autofill;
import static android.ext.services.autofill.ExactMatch.calculateScore;
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.assertThrows;
import android.os.Bundle;
import android.view.autofill.AutofillValue;
import org.junit.Test;
public class ExactMatchTest {
private Bundle last4Bundle() {
final Bundle bundle = new Bundle();
bundle.putInt("suffix", 4);
return bundle;
}
@Test
public void testCalculateScore_nullValue() {
assertFloat(calculateScore(null, "TEST", null), 0);
}
@Test
public void testCalculateScore_nonTextValue() {
assertFloat(calculateScore(AutofillValue.forToggle(true), "TEST", null), 0);
}
@Test
public void testCalculateScore_nullUserData() {
assertFloat(calculateScore(AutofillValue.forText("TEST"), null, null), 0);
}
@Test
public void testCalculateScore_succeedMatchMixedCases_last4() {
final Bundle last4 = last4Bundle();
assertFloat(calculateScore(AutofillValue.forText("TEST"), "1234 test", last4), 1);
assertFloat(calculateScore(AutofillValue.forText("test"), "1234 TEST", last4), 1);
}
@Test
public void testCalculateScore_mismatchDifferentSizes_last4() {
final Bundle last4 = last4Bundle();
assertFloat(calculateScore(AutofillValue.forText("TEST"), "TEST1", last4), 0);
assertFloat(calculateScore(AutofillValue.forText(""), "TEST", last4), 0);
assertFloat(calculateScore(AutofillValue.forText("TEST"), "", last4), 0);
}
@Test
public void testCalculateScore_match() {
final Bundle last4 = last4Bundle();
assertFloat(calculateScore(AutofillValue.forText("1234 1234 1234 1234"),
"xxxx xxxx xxxx 1234", last4), 1);
assertFloat(calculateScore(AutofillValue.forText("TEST"), "TEST", null), 1);
assertFloat(calculateScore(AutofillValue.forText("TEST 1234"), "1234", last4), 1);
assertFloat(calculateScore(AutofillValue.forText("TEST"), "test", null), 1);
}
@Test
public void testCalculateScore_badBundle() {
final Bundle bundle = new Bundle();
bundle.putInt("suffix", -2);
assertThrows(IllegalArgumentException.class, () -> calculateScore(
AutofillValue.forText("TEST"), "TEST", bundle));
final Bundle largeBundle = new Bundle();
largeBundle.putInt("suffix", 10);
assertFloat(calculateScore(AutofillValue.forText("TEST"), "TEST", largeBundle), 1);
final Bundle stringBundle = new Bundle();
stringBundle.putString("suffix", "value");
assertThrows(IllegalArgumentException.class, () -> calculateScore(
AutofillValue.forText("TEST"), "TEST", stringBundle));
}
public static void assertFloat(float actualValue, float expectedValue) {
assertThat(actualValue).isWithin(0.01F).of(expectedValue);
}
}

View File

@@ -484,15 +484,15 @@ public final class AutofillManagerService
}
// Called by Shell command.
void getScore(@Nullable String algorithmName, @NonNull String value1,
void calculateScore(@Nullable String algorithmName, @NonNull String value1,
@NonNull String value2, @NonNull RemoteCallback callback) {
enforceCallingPermissionForManagement();
final FieldClassificationStrategy strategy =
new FieldClassificationStrategy(getContext(), UserHandle.USER_CURRENT);
strategy.getScores(callback, algorithmName, null,
Arrays.asList(AutofillValue.forText(value1)), new String[] { value2 });
strategy.calculateScores(callback, Arrays.asList(AutofillValue.forText(value1)),
new String[] { value2 }, null, algorithmName, null, null, null);
}
// Called by Shell command.

View File

@@ -233,7 +233,7 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand {
final String value2 = getNextArgRequired();
final CountDownLatch latch = new CountDownLatch(1);
mService.getScore(algorithm, value1, value2, new RemoteCallback((result) -> {
mService.calculateScore(algorithm, value1, value2, new RemoteCallback((result) -> {
final Scores scores = result.getParcelable(EXTRA_SCORES);
if (scores == null) {
pw.println("no score");

View File

@@ -15,11 +15,12 @@
*/
package com.android.server.autofill;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
import static android.service.autofill.AutofillFieldClassificationService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS;
import static android.service.autofill.AutofillFieldClassificationService.SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
import android.Manifest;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -40,6 +41,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.service.autofill.AutofillFieldClassificationService;
import android.service.autofill.IAutofillFieldClassificationService;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.view.autofill.AutofillValue;
@@ -257,13 +259,13 @@ final class FieldClassificationStrategy {
return parser.get(res, resourceId);
}
//TODO(b/70291841): rename this method (and all others in the chain) to something like
// calculateScores() ?
void getScores(RemoteCallback callback, @Nullable String algorithmName,
@Nullable Bundle algorithmArgs, @NonNull List<AutofillValue> actualValues,
@NonNull String[] userDataValues) {
connectAndRun((service) -> service.getScores(callback, algorithmName,
algorithmArgs, actualValues, userDataValues));
void calculateScores(RemoteCallback callback, @NonNull List<AutofillValue> actualValues,
@NonNull String[] userDataValues, @NonNull String[] categoryIds,
@Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs,
@Nullable ArrayMap<String, String> algorithms,
@Nullable ArrayMap<String, Bundle> args) {
connectAndRun((service) -> service.calculateScores(callback, actualValues,
userDataValues, categoryIds, defaultAlgorithm, defaultArgs, algorithms, args));
}
void dump(String prefix, PrintWriter pw) {

View File

@@ -1394,6 +1394,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final String[] userValues = userData.getValues();
final String[] categoryIds = userData.getCategoryIds();
final String defaultAlgorithm = userData.getFieldClassificationAlgorithm();
final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
final ArrayMap<String, String> algorithms = userData.getFieldClassificationAlgorithms();
final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
// Sanity check
if (userValues == null || categoryIds == null || userValues.length != categoryIds.length) {
final int valuesLength = userValues == null ? -1 : userValues.length;
@@ -1409,8 +1415,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final ArrayList<FieldClassification> detectedFieldClassifications = new ArrayList<>(
maxFieldsSize);
final String algorithm = userData.getFieldClassificationAlgorithm();
final Bundle algorithmArgs = userData.getAlgorithmArgs();
final int viewsSize = viewStates.size();
// First, we get all scores.
@@ -1497,7 +1501,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mComponentName, mCompatMode);
});
fcStrategy.getScores(callback, algorithm, algorithmArgs, currentValues, userValues);
fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds,
defaultAlgorithm, defaultArgs, algorithms, args);
}
/**