From 71d53e0cc8debebe82341241c020de927c3959cd Mon Sep 17 00:00:00 2001 From: Adam He Date: Tue, 6 Nov 2018 14:03:26 -0800 Subject: [PATCH] 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 --- api/current.txt | 2 + api/system-current.txt | 5 +- api/test-current.txt | 13 ++ .../AutofillFieldClassificationService.java | 121 +++++++++++++- .../IAutofillFieldClassificationService.aidl | 7 +- .../android/service/autofill/UserData.java | 157 ++++++++++++++---- packages/ExtServices/res/values/strings.xml | 1 + ...utofillFieldClassificationServiceImpl.java | 67 ++++++-- .../services/autofill/EditDistanceScorer.java | 29 +--- .../ext/services/autofill/ExactMatch.java | 67 ++++++++ packages/ExtServices/tests/Android.mk | 3 +- ...illFieldClassificationServiceImplTest.java | 91 ++++++++-- .../autofill/EditDistanceScorerTest.java | 94 +++-------- .../ext/services/autofill/ExactMatchTest.java | 98 +++++++++++ .../autofill/AutofillManagerService.java | 6 +- .../AutofillManagerServiceShellCommand.java | 2 +- .../autofill/FieldClassificationStrategy.java | 20 ++- .../com/android/server/autofill/Session.java | 11 +- 18 files changed, 615 insertions(+), 179 deletions(-) create mode 100644 packages/ExtServices/src/android/ext/services/autofill/ExactMatch.java create mode 100644 packages/ExtServices/tests/src/android/ext/services/autofill/ExactMatchTest.java diff --git a/api/current.txt b/api/current.txt index 2ef4a392ef230..eeee2477f2dd4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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 { diff --git a/api/system-current.txt b/api/system-current.txt index 57b7eed0b4dac..892cdea4c35e7 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -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, java.util.List); + method public float[][] onCalculateScores(java.util.List, java.util.List, java.util.List, 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, java.util.List); + 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"; diff --git a/api/test-current.txt b/api/test-current.txt index 46cbb52f6efa4..55c8df82eae12 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -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 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); diff --git a/core/java/android/service/autofill/AutofillFieldClassificationService.java b/core/java/android/service/autofill/AutofillFieldClassificationService.java index 1cd76d2e9ec9f..834ec4d24a9e2 100644 --- a/core/java/android/service/autofill/AutofillFieldClassificationService.java +++ b/core/java/android/service/autofill/AutofillFieldClassificationService.java @@ -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. + * + *

Service implementation must provide this algorithm.

+ */ + 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. + * + *

Service implementation must provide this algorithm.

+ */ + 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 actualValues, String[] userDataValues) { + private void calculateScores(RemoteCallback callback, List 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 actualValues, @NonNull List userDataValues) { @@ -179,16 +200,98 @@ public abstract class AutofillFieldClassificationService extends Service { return null; } + /** + * Calculates field classification scores in a batch. + * + *

A field classification score is a {@code float} representing how well an + * {@link AutofillValue} matches a expected value predicted by an autofill service + * —a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}. + * + *

The exact score depends on the algorithm used to calculate it—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). + * + *

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: + * + *

+     * 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);
+     * 
+ * + *

Returns: + * + *

+     * [
+     *   [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
+     *   [0.0, 0.0]  // "PHONE1" compared against ["email1", "phone1"]
+     * ];
+     * 
+ * + *

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: + * + *

+     * 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);
+     * 
+ * + *

Returns: + * + *

+     * [
+     *   [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
+     *   [0.0, 1.0]  // "PHONE1" compared against ["email1", "phone1"]
+     * ];
+     * 
+ * + * @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 actualValues, + @NonNull List userDataValues, @NonNull List 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 actualValues, String[] userDataValues) - throws RemoteException { + public void calculateScores(RemoteCallback callback, List 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)); } } diff --git a/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl b/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl index 398557d5ad2e6..2cd24f96a22d6 100644 --- a/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl +++ b/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl @@ -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 actualValues, in String[] userDataValues); + void calculateScores(in RemoteCallback callback, in List actualValues, + in String[] userDataValues, in String[] categoryIds, + in String defaultAlgorithm, in Bundle defaultArgs, + in Map algorithms, in Map args); } diff --git a/core/java/android/service/autofill/UserData.java b/core/java/android/service/autofill/UserData.java index fccb85b957fa6..37f192366f813 100644 --- a/core/java/android/service/autofill/UserData.java +++ b/core/java/android/service/autofill/UserData.java @@ -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 mCategoryAlgorithms; + private final ArrayMap 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 getFieldClassificationAlgorithms() { + return mCategoryAlgorithms; + } + + /** @hide */ + public ArrayMap 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 mCategoryIds; private final ArrayList 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 mCategoryAlgorithms; + private ArrayMap 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 mUniqueValueCategoryPairs; - /** * Creates a new builder for the user data used for field * classification. @@ -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: *
    @@ -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 field classification. + * Sets the default algorithm used for + * field classification. * *

    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 field classification + * for the specified category. + * + *

    The currently available algorithms can be retrieved through + * {@link AutofillManager#getAvailableFieldClassificationAlgorithms()}. + * + *

    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 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 categoryAlgorithms = new ArrayMap<>(); + parcel.readMap(categoryAlgorithms, String.class.getClassLoader()); + final ArrayMap 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(); } diff --git a/packages/ExtServices/res/values/strings.xml b/packages/ExtServices/res/values/strings.xml index 617e49ac11203..a9a5450c536e8 100644 --- a/packages/ExtServices/res/values/strings.xml +++ b/packages/ExtServices/res/values/strings.xml @@ -22,5 +22,6 @@ EDIT_DISTANCE EDIT_DISTANCE + EXACT_MATCH diff --git a/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java b/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java index 9ba7e092f34b2..e379db842b3ba 100644 --- a/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java +++ b/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java @@ -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 actualValues, - @NonNull List userDataValues) { + /** @hide */ + public float[][] onCalculateScores(@NonNull List actualValues, + @NonNull List userDataValues, @NonNull List 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) algorithms, + (HashMap) args); + } + + /** @hide */ + public float[][] calculateScores(@NonNull List actualValues, + @NonNull List userDataValues, @NonNull List categoryIds, + @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs, + @Nullable HashMap algorithms, + @Nullable HashMap 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; } } diff --git a/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java b/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java index 302b16022c267..6a47901aa58ec 100644 --- a/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java +++ b/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java @@ -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. * *

    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 actualValues, - @NonNull List 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; - } } diff --git a/packages/ExtServices/src/android/ext/services/autofill/ExactMatch.java b/packages/ExtServices/src/android/ext/services/autofill/ExactMatch.java new file mode 100644 index 0000000000000..3e55c5c59e024 --- /dev/null +++ b/packages/ExtServices/src/android/ext/services/autofill/ExactMatch.java @@ -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; + } + } +} diff --git a/packages/ExtServices/tests/Android.mk b/packages/ExtServices/tests/Android.mk index 0a95b858a93e0..a57fa9458f085 100644 --- a/packages/ExtServices/tests/Android.mk +++ b/packages/ExtServices/tests/Android.mk @@ -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) diff --git a/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java b/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java index 48c076e67e784..6fda4c73792b2 100644 --- a/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java @@ -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 actualValues = Arrays.asList(forText("A"), forText("b"), + forText("dude")); + final List userDataValues = Arrays.asList("a", "b", "B", "ab", "c", "dude", + "sweet_dude", "dude_sweet"); + final List categoryIds = Arrays.asList("cat", "cat", "cat", "cat", "cat", "last4", + "last4", "last4"); + final HashMap algorithms = new HashMap<>(1); + algorithms.put("last4", REQUIRED_ALGORITHM_EXACT_MATCH); + + final Bundle last4Bundle = new Bundle(); + last4Bundle.putInt("suffix", 4); + + final HashMap 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(); } } diff --git a/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java b/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java index afe223641d375..9b9d4be59929e 100644 --- a/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java @@ -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 actualValues = Arrays.asList(forText("A"), forText("b")); - final List 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(); - } + } diff --git a/packages/ExtServices/tests/src/android/ext/services/autofill/ExactMatchTest.java b/packages/ExtServices/tests/src/android/ext/services/autofill/ExactMatchTest.java new file mode 100644 index 0000000000000..bf5e1609fa8bd --- /dev/null +++ b/packages/ExtServices/tests/src/android/ext/services/autofill/ExactMatchTest.java @@ -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); + } +} diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index e8887e7a2ebe1..612c9294f5d11 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -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. diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java index 35c51027df86b..c562fb1b7dde3 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java @@ -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"); diff --git a/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java b/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java index 293f908e27089..9db6254a8baab 100644 --- a/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java +++ b/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java @@ -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 actualValues, - @NonNull String[] userDataValues) { - connectAndRun((service) -> service.getScores(callback, algorithmName, - algorithmArgs, actualValues, userDataValues)); + void calculateScores(RemoteCallback callback, @NonNull List actualValues, + @NonNull String[] userDataValues, @NonNull String[] categoryIds, + @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs, + @Nullable ArrayMap algorithms, + @Nullable ArrayMap args) { + connectAndRun((service) -> service.calculateScores(callback, actualValues, + userDataValues, categoryIds, defaultAlgorithm, defaultArgs, algorithms, args)); } void dump(String prefix, PrintWriter pw) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index d76a5dff36d39..d87ad97889c09 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -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 algorithms = userData.getFieldClassificationAlgorithms(); + final ArrayMap 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 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); } /**