Implemented multiple matches on FieldClassification.getMatches()

Test: atest CtsAutoFillServiceTestCases:FieldsClassificationTest

Bug:70291841

Change-Id: Icc015d7c76f0f11e398c3093b4ea070c8f35f589
diff --git a/core/java/android/service/autofill/FieldClassification.java b/core/java/android/service/autofill/FieldClassification.java
index b28c6f8..001e3a0 100644
--- a/core/java/android/service/autofill/FieldClassification.java
+++ b/core/java/android/service/autofill/FieldClassification.java
@@ -25,8 +25,9 @@
 
 import com.android.internal.util.Preconditions;
 
-import com.google.android.collect.Lists;
-
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 /**
@@ -36,15 +37,24 @@
 // TODO(b/70291841): let caller handle Parcelable...
 public final class FieldClassification implements Parcelable {
 
-    private final Match mMatch;
+    private final ArrayList<Match> mMatches;
 
     /** @hide */
-    public FieldClassification(@NonNull Match match) {
-        mMatch = Preconditions.checkNotNull(match);
+    public FieldClassification(@NonNull ArrayList<Match> matches) {
+        mMatches = Preconditions.checkNotNull(matches);
+        Collections.sort(mMatches, new Comparator<Match>() {
+            @Override
+            public int compare(Match o1, Match o2) {
+                if (o1.mScore > o2.mScore) return -1;
+                if (o1.mScore < o2.mScore) return 1;
+                return 0;
+            }}
+        );
     }
 
     /**
-     * Gets the {@link Match matches} with the highest {@link Match#getScore() scores}.
+     * Gets the {@link Match matches} with the highest {@link Match#getScore() scores} (sorted in
+     * descending order).
      *
      * <p><b>Note:</b> There's no guarantee of how many matches will be returned. In fact,
      * the Android System might return just the top match to minimize the impact of field
@@ -52,14 +62,14 @@
      */
     @NonNull
     public List<Match> getMatches() {
-        return Lists.newArrayList(mMatch);
+        return mMatches;
     }
 
     @Override
     public String toString() {
         if (!sDebug) return super.toString();
 
-        return "FieldClassification: " + mMatch;
+        return "FieldClassification: " + mMatches;
     }
 
     /////////////////////////////////////
@@ -73,7 +83,10 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        mMatch.writeToParcel(parcel);
+        parcel.writeInt(mMatches.size());
+        for (int i = 0; i < mMatches.size(); i++) {
+            mMatches.get(i).writeToParcel(parcel);
+        }
     }
 
     public static final Parcelable.Creator<FieldClassification> CREATOR =
@@ -81,7 +94,13 @@
 
         @Override
         public FieldClassification createFromParcel(Parcel parcel) {
-            return new FieldClassification(Match.readFromParcel(parcel));
+            final int size = parcel.readInt();
+            final ArrayList<Match> matches = new ArrayList<>();
+            for (int i = 0; i < size; i++) {
+                matches.add(i, Match.readFromParcel(parcel));
+            }
+
+            return new FieldClassification(matches);
         }
 
         @Override
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 07fab61..2eb44cf 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -25,7 +25,6 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.service.autofill.FieldClassification.Match;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -157,7 +156,7 @@
                 final AutofillId[] detectedFields = event.mDetectedFieldIds;
                 parcel.writeParcelableArray(detectedFields, flags);
                 if (detectedFields != null) {
-                    Match.writeArrayToParcel(parcel, event.mDetectedMatches);
+                    parcel.writeParcelableArray(event.mDetectedFieldClassifications, flags);
                 }
             }
         }
@@ -251,7 +250,7 @@
         @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds;
 
         @Nullable private final AutofillId[] mDetectedFieldIds;
-        @Nullable private final Match[] mDetectedMatches;
+        @Nullable private final FieldClassification[] mDetectedFieldClassifications;
 
         /**
          * Returns the type of the event.
@@ -370,11 +369,11 @@
             final ArrayMap<AutofillId, FieldClassification> map = new ArrayMap<>(size);
             for (int i = 0; i < size; i++) {
                 final AutofillId id = mDetectedFieldIds[i];
-                final Match match = mDetectedMatches[i];
+                final FieldClassification fc = mDetectedFieldClassifications[i];
                 if (sVerbose) {
-                    Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", match=" + match);
+                    Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", fc=" + fc);
                 }
-                map.put(id, new FieldClassification(match));
+                map.put(id, fc);
             }
             return map;
         }
@@ -455,7 +454,7 @@
          * and belonged to datasets.
          * @param manuallyFilledDatasetIds The ids of datasets that had values matching the
          * respective entry on {@code manuallyFilledFieldIds}.
-         * @param detectedMatches the field classification matches.
+         * @param detectedFieldClassifications the field classification matches.
          *
          * @throws IllegalArgumentException If the length of {@code changedFieldIds} and
          * {@code changedDatasetIds} doesn't match.
@@ -471,7 +470,8 @@
                 @Nullable ArrayList<String> changedDatasetIds,
                 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
                 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
-                @Nullable AutofillId[] detectedFieldIds, @Nullable Match[] detectedMatches) {
+                @Nullable AutofillId[] detectedFieldIds,
+                @Nullable FieldClassification[] detectedFieldClassifications) {
             mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED,
                     "eventType");
             mDatasetId = datasetId;
@@ -496,7 +496,7 @@
             mManuallyFilledDatasetIds = manuallyFilledDatasetIds;
 
             mDetectedFieldIds = detectedFieldIds;
-            mDetectedMatches = detectedMatches;
+            mDetectedFieldClassifications = detectedFieldClassifications;
         }
 
         @Override
@@ -510,7 +510,8 @@
                     + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds
                     + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds
                     + ", detectedFieldIds=" + Arrays.toString(mDetectedFieldIds)
-                    + ", detectedMaches =" + Arrays.toString(mDetectedMatches)
+                    + ", detectedFieldClassifications ="
+                        + Arrays.toString(mDetectedFieldClassifications)
                     + "]";
         }
     }
@@ -548,15 +549,16 @@
                         }
                         final AutofillId[] detectedFieldIds = parcel.readParcelableArray(null,
                                 AutofillId.class);
-                        final Match[] detectedMatches = (detectedFieldIds != null)
-                                ? Match.readArrayFromParcel(parcel)
+                        final FieldClassification[] detectedFieldClassifications =
+                                (detectedFieldIds != null)
+                                ? parcel.readParcelableArray(null, FieldClassification.class)
                                 : null;
 
                         selection.addEvent(new Event(eventType, datasetId, clientState,
                                 selectedDatasetIds, ignoredDatasets,
                                 changedFieldIds, changedDatasetIds,
                                 manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                                detectedFieldIds, detectedMatches));
+                                detectedFieldIds, detectedFieldClassifications));
                     }
                     return selection;
                 }
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 4bf3c5a..3361824 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -52,6 +52,7 @@
 import android.provider.Settings;
 import android.service.autofill.AutofillService;
 import android.service.autofill.AutofillServiceInfo;
+import android.service.autofill.FieldClassification;
 import android.service.autofill.FieldClassification.Match;
 import android.service.autofill.FillEventHistory;
 import android.service.autofill.FillEventHistory.Event;
@@ -81,6 +82,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Random;
 
 /**
@@ -720,37 +722,45 @@
             @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
             @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
             @Nullable ArrayList<AutofillId> detectedFieldIdsList,
-            @Nullable ArrayList<Match> detectedMatchesList,
+            @Nullable ArrayList<FieldClassification> detectedFieldClassificationsList,
             @NonNull String appPackageName) {
-
         synchronized (mLock) {
             if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
                 AutofillId[] detectedFieldsIds = null;
-                Match[] detectedMatches = null;
+                FieldClassification[] detectedFieldClassifications = null;
                 if (detectedFieldIdsList != null) {
                     detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()];
                     detectedFieldIdsList.toArray(detectedFieldsIds);
-                    detectedMatches = new Match[detectedMatchesList.size()];
-                    detectedMatchesList.toArray(detectedMatches);
+                    detectedFieldClassifications =
+                            new FieldClassification[detectedFieldClassificationsList.size()];
+                    detectedFieldClassificationsList.toArray(detectedFieldClassifications);
 
-                    final int size = detectedMatchesList.size();
+                    final int numberFields = detectedFieldsIds.length;
+                    int totalSize = 0;
                     float totalScore = 0;
-                    for (int i = 0; i < size; i++) {
-                        totalScore += detectedMatches[i].getScore();
+                    for (int i = 0; i < numberFields; i++) {
+                        final FieldClassification fc = detectedFieldClassifications[i];
+                        final List<Match> matches = fc.getMatches();
+                        final int size = matches.size();
+                        totalSize += size;
+                        for (int j = 0; j < size; j++) {
+                            totalScore += matches.get(j).getScore();
+                        }
                     }
-                    final int averageScore = (int) ((totalScore * 100) / size);
-                    mMetricsLogger.write(
-                            Helper.newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
-                                    appPackageName, getServicePackageName())
-                            .setCounterValue(size)
-                            .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE, averageScore));
 
+                    final int averageScore = (int) ((totalScore * 100) / totalSize);
+                    mMetricsLogger.write(Helper
+                            .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
+                                    appPackageName, getServicePackageName())
+                            .setCounterValue(numberFields)
+                            .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE,
+                                    averageScore));
                 }
                 mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
                         clientState, selectedDatasets, ignoredDatasets,
                         changedFieldIds, changedDatasetIds,
                         manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                        detectedFieldsIds, detectedMatches));
+                        detectedFieldsIds, detectedFieldClassifications));
             }
         }
     }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index ceae93c..7b85a6c 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -66,6 +66,7 @@
 import android.service.autofill.UserData;
 import android.service.autofill.ValueFinder;
 import android.service.autofill.EditDistanceScorer;
+import android.service.autofill.FieldClassification;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LocalLog;
@@ -961,15 +962,15 @@
         final UserData userData = mService.getUserData();
 
         final ArrayList<AutofillId> detectedFieldIds;
-        final ArrayList<Match> detectedMatches;
+        final ArrayList<FieldClassification> detectedFieldClassifications;
 
         if (userData != null) {
             final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize();
             detectedFieldIds = new ArrayList<>(maxFieldsSize);
-            detectedMatches = new ArrayList<>(maxFieldsSize);
+            detectedFieldClassifications = new ArrayList<>(maxFieldsSize);
         } else {
             detectedFieldIds = null;
-            detectedMatches = null;
+            detectedFieldClassifications = null;
         }
 
         for (int i = 0; i < mViewStates.size(); i++) {
@@ -1078,8 +1079,8 @@
 
                     // Sets field classification score for field
                     if (userData!= null) {
-                        setScore(detectedFieldIds, detectedMatches, userData, viewState.id,
-                                currentValue);
+                        setScore(detectedFieldIds, detectedFieldClassifications, userData,
+                                viewState.id, currentValue);
                     }
                 } // else
             } // else
@@ -1093,7 +1094,7 @@
                     + ", changedDatasetIds=" + changedDatasetIds
                     + ", manuallyFilledIds=" + manuallyFilledIds
                     + ", detectedFieldIds=" + detectedFieldIds
-                    + ", detectedMatches=" + detectedMatches
+                    + ", detectedFieldClassifications=" + detectedFieldClassifications
                     );
         }
 
@@ -1116,16 +1117,17 @@
         mService.logContextCommitted(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
                 changedFieldIds, changedDatasetIds,
                 manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                detectedFieldIds, detectedMatches, mComponentName.getPackageName());
+                detectedFieldIds, detectedFieldClassifications, mComponentName.getPackageName());
     }
 
     /**
-     * Adds the top score match to {@code detectedFieldsIds} and {@code detectedMatches} for
+     * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for
      * {@code fieldId} based on its {@code currentValue} and {@code userData}.
      */
     private static void setScore(@NonNull ArrayList<AutofillId> detectedFieldIds,
-            @NonNull ArrayList<Match> detectedMatches, @NonNull UserData userData,
-            @NonNull AutofillId fieldId, @NonNull AutofillValue currentValue) {
+            @NonNull ArrayList<FieldClassification> detectedFieldClassifications,
+            @NonNull UserData userData, @NonNull AutofillId fieldId,
+            @NonNull AutofillValue currentValue) {
 
         final String[] userValues = userData.getValues();
         final String[] remoteIds = userData.getRemoteIds();
@@ -1138,23 +1140,26 @@
                     + valuesLength + ", ids.length = " + idsLength);
             return;
         }
-        String remoteId = null;
-        float topScore = 0;
+
+        ArrayList<Match> matches = null;
         for (int i = 0; i < userValues.length; i++) {
+            String remoteId = remoteIds[i];
             final String value = userValues[i];
             final float score = userData.getScorer().getScore(currentValue, value);
-            if (score > topScore) {
-                topScore = score;
-                remoteId = remoteIds[i];
+            if (score > 0) {
+                if (sVerbose) {
+                    Slog.v(TAG, "adding score " + score + " at index " + i + " and id " + fieldId);
+                }
+                if (matches == null) {
+                    matches = new ArrayList<>(userValues.length);
+                }
+                matches.add(new Match(remoteId, score));
             }
+            else if (sVerbose) Slog.v(TAG, "skipping score 0 at index " + i + " and id " + fieldId);
         }
-
-        if (remoteId != null && topScore > 0) {
-            if (sVerbose) Slog.v(TAG, "setScores(): top score for #" + fieldId + " is " + topScore);
+        if (matches != null) {
             detectedFieldIds.add(fieldId);
-            detectedMatches.add(new Match(remoteId, topScore));
-        } else if (sVerbose) {
-            Slog.v(TAG, "setScores(): no top score for #" + fieldId + ": " + topScore);
+            detectedFieldClassifications.add(new FieldClassification(matches));
         }
     }