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));
}
}