Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5672defa authored by Felipe Leme's avatar Felipe Leme
Browse files

Implemented autofill field classification on multiple fields and user data.

Test: atest CtsAutoFillServiceTestCases:FieldsClassificationTest
Test: atest CtsAutoFillServiceTestCases:FieldsClassificationScorerTest
Test: atest CtsAutoFillServiceTestCases:UserDataTest

Bug: 68045531

Change-Id: Ia9252cb5b84236a76a1419f4a2669b2e933f5177
parent bb6bfea6
Loading
Loading
Loading
Loading
+17 −2
Original line number Diff line number Diff line
@@ -38,9 +38,24 @@ public final class FieldsClassificationScorer {
     * partial mathces are something in between, typically using edit-distance algorithms.
     */
    public static int getScore(@NonNull AutofillValue actualValue, @NonNull String userData) {
        // TODO(b/67867469): implement edit distance - currently it's returning either 0 or 100%
        if (actualValue == null || !actualValue.isText() || userData == null) return 0;
        return actualValue.getTextValue().toString().equalsIgnoreCase(userData) ? MAX_VALUE : 0;
        // TODO(b/67867469): implement edit distance - currently it's returning either 0, 100%, or
        // partial match when number of chars match
        final String textValue = actualValue.getTextValue().toString();
        final int total = textValue.length();
        if (total != userData.length()) return 0;

        int matches = 0;
        for (int i = 0; i < total; i++) {
            if (Character.toLowerCase(textValue.charAt(i)) == Character
                    .toLowerCase(userData.charAt(i))) {
                matches++;
            }
        }

        final float percentage = ((float) matches) / total;
        final int rounded = (int) (percentage * MAX_VALUE);
        return rounded;
    }

    private FieldsClassificationScorer() {
+35 −46
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.service.autofill;

import static android.view.autofill.Helper.sVerbose;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -24,8 +26,10 @@ import android.content.IntentSender;
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;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;

@@ -35,10 +39,10 @@ import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
@@ -58,6 +62,8 @@ import java.util.Set;
 * the history will clear out after some pre-defined time).
 */
public final class FillEventHistory implements Parcelable {
    private static final String TAG = "FillEventHistory";

    /**
     * Not in parcel. The ID of the autofill session that created the {@link FillResponse}.
     */
@@ -149,10 +155,10 @@ public final class FillEventHistory implements Parcelable {
                        parcel.writeStringList(event.mManuallyFilledDatasetIds.get(j));
                    }
                }
                parcel.writeParcelable(event.mDetectedFieldId, flags);
                if (event.mDetectedRemoteId != null) {
                    parcel.writeString(event.mDetectedRemoteId);
                    parcel.writeInt(event.mDetectedFieldScore);
                final AutofillId[] detectedFields = event.mDetectedFieldIds;
                parcel.writeParcelableArray(detectedFields, flags);
                if (detectedFields != null) {
                    parcel.writeParcelableArray(event.mDetectedMatches, flags);
                }
            }
        }
@@ -244,10 +250,8 @@ public final class FillEventHistory implements Parcelable {
        @Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds;
        @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds;

        // TODO(b/67867469): store list of fields instead of hardcoding just one
        @Nullable private final AutofillId mDetectedFieldId;
        @Nullable private final String mDetectedRemoteId;
        private final int mDetectedFieldScore;
        @Nullable private final AutofillId[] mDetectedFieldIds;
        @Nullable private final Match[] mDetectedMatches;

        /**
         * Returns the type of the event.
@@ -365,15 +369,19 @@ public final class FillEventHistory implements Parcelable {
         */
        @TestApi
        @NonNull public Map<AutofillId, FieldClassification> getFieldsClassification() {
            if (mDetectedFieldId == null || mDetectedRemoteId == null
                    || mDetectedFieldScore == -1) {
            if (mDetectedFieldIds == null) {
                return Collections.emptyMap();
            }

            final ArrayMap<AutofillId, FieldClassification> map = new ArrayMap<>(1);
            map.put(mDetectedFieldId,
                    new FieldClassification(new FieldClassification.Match(
                            mDetectedRemoteId, mDetectedFieldScore)));
            final int size = mDetectedFieldIds.length;
            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];
                if (sVerbose) {
                    Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", match=" + match);
                }
                map.put(id, new FieldClassification(match));
            }
            return map;
        }

@@ -468,7 +476,7 @@ public final class FillEventHistory implements Parcelable {
                @Nullable ArrayList<String> changedDatasetIds,
                @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
                @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
                @Nullable Map<AutofillId, FieldClassification> fieldsClassification) {
                @Nullable AutofillId[] detectedFieldIds, @Nullable Match[] detectedMaches) {
            mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED,
                    "eventType");
            mDatasetId = datasetId;
@@ -492,20 +500,8 @@ public final class FillEventHistory implements Parcelable {
            mManuallyFilledFieldIds = manuallyFilledFieldIds;
            mManuallyFilledDatasetIds = manuallyFilledDatasetIds;

            // TODO(b/67867469): store list of fields instead of hardcoding just one
            if (fieldsClassification == null) {
                mDetectedFieldId = null;
                mDetectedRemoteId = null;
                mDetectedFieldScore = 0;

            } else {
                final Entry<AutofillId, FieldClassification> tmpEntry = fieldsClassification
                        .entrySet().iterator().next();
                final FieldClassification.Match tmpMatch = tmpEntry.getValue().getTopMatch();
                mDetectedFieldId = tmpEntry.getKey();
                mDetectedRemoteId = tmpMatch.getRemoteId();
                mDetectedFieldScore = tmpMatch.getScore();
            }
            mDetectedFieldIds = detectedFieldIds;
            mDetectedMatches = detectedMaches;
        }

        @Override
@@ -518,9 +514,8 @@ public final class FillEventHistory implements Parcelable {
                    + ", changedDatasetsIds=" + mChangedDatasetIds
                    + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds
                    + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds
                    + ", detectedFieldId=" + mDetectedFieldId
                    + ", detectedRemoteId=" + mDetectedRemoteId
                    + ", detectedFieldScore=" + mDetectedFieldScore
                    + ", detectedFieldIds=" + Arrays.toString(mDetectedFieldIds)
                    + ", detectedMaches =" + Arrays.toString(mDetectedMatches)
                    + "]";
        }
    }
@@ -556,23 +551,17 @@ public final class FillEventHistory implements Parcelable {
                        } else {
                            manuallyFilledDatasetIds = null;
                        }
                        // TODO(b/67867469): store list of fields instead of hardcoding just one
                        ArrayMap<AutofillId, FieldClassification> fieldsClassification = null;
                        final AutofillId detectedFieldId = parcel.readParcelable(null);
                        if (detectedFieldId == null) {
                            fieldsClassification = null;
                        } else {
                            fieldsClassification = new ArrayMap<AutofillId, FieldClassification>(1);
                            fieldsClassification.put(detectedFieldId,
                                    new FieldClassification(new FieldClassification.Match(
                                            parcel.readString(), parcel.readInt())));
                        }
                        final AutofillId[] detectedFieldIds = parcel.readParcelableArray(null,
                                AutofillId.class);
                        final Match[] detectedMatches = (detectedFieldIds != null)
                                ? parcel.readParcelableArray(null, Match.class)
                                : null;

                        selection.addEvent(new Event(eventType, datasetId, clientState,
                                selectedDatasetIds, ignoredDatasets,
                                changedFieldIds, changedDatasetIds,
                                manuallyFilledFieldIds, manuallyFilledDatasetIds,
                                fieldsClassification));
                                detectedFieldIds, detectedMatches));
                    }
                    return selection;
                }
+14 −5
Original line number Diff line number Diff line
@@ -29,7 +29,6 @@ import android.content.ContentResolver;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.Log;
import android.view.autofill.Helper;

@@ -79,10 +78,16 @@ public final class UserData implements Parcelable {

    /** @hide */
    public void dump(String prefix, PrintWriter pw) {
        // Cannot disclose remote ids because they could contain PII
        // Cannot disclose remote ids or values because they could contain PII
        pw.print(prefix); pw.print("Remote ids size: "); pw.println(mRemoteIds.length);
        for (int i = 0; i < mRemoteIds.length; i++) {
            pw.print(prefix); pw.print(prefix); pw.print(i); pw.print(": ");
            pw.println(Helper.getRedacted(mRemoteIds[i]));
        }
        pw.print(prefix); pw.print("Values size: "); pw.println(mValues.length);
        for (int i = 0; i < mValues.length; i++) {
            pw.print(prefix); pw.print(prefix); pw.print(i); pw.print(": "); pw.println(mValues[i]);
            pw.print(prefix); pw.print(prefix); pw.print(i); pw.print(": ");
            pw.println(Helper.getRedacted(mValues[i]));
        }
    }

@@ -104,7 +109,7 @@ public final class UserData implements Parcelable {
     */
    @TestApi
    public static final class Builder {
        private final ArraySet<String> mRemoteIds;
        private final ArrayList<String> mRemoteIds;
        private final ArrayList<String> mValues;
        private boolean mDestroyed;

@@ -120,7 +125,7 @@ public final class UserData implements Parcelable {
            checkValidRemoteId(remoteId);
            checkValidValue(value);
            final int capacity = getMaxUserDataSize();
            mRemoteIds = new ArraySet<>(capacity);
            mRemoteIds = new ArrayList<>(capacity);
            mValues = new ArrayList<>(capacity);
            mRemoteIds.add(remoteId);
            mValues.add(value);
@@ -149,10 +154,14 @@ public final class UserData implements Parcelable {
            Preconditions.checkState(!mRemoteIds.contains(remoteId),
                    // Don't include remoteId on message because it could contain PII
                    "already has entry with same remoteId");
            Preconditions.checkState(!mValues.contains(value),
                    // Don't include remoteId on message because it could contain PII
                    "already has entry with same value");
            Preconditions.checkState(mRemoteIds.size() < getMaxUserDataSize(),
                    "already added " + mRemoteIds.size() + " elements");
            mRemoteIds.add(remoteId);
            mValues.add(value);

            return this;
        }

+9 −31
Original line number Diff line number Diff line
@@ -18,11 +18,6 @@ package android.view.autofill;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;

import java.util.Arrays;
import java.util.Objects;
import java.util.Set;

/** @hide */
public final class Helper {
@@ -31,37 +26,20 @@ public final class Helper {
    public static boolean sDebug = false;
    public static boolean sVerbose = false;

    public static final String REDACTED = "[REDACTED]";

    static StringBuilder append(StringBuilder builder, Bundle bundle) {
        if (bundle == null || !sDebug) {
            builder.append("N/A");
        } else if (!sVerbose) {
            builder.append(REDACTED);
        } else {
            final Set<String> keySet = bundle.keySet();
            builder.append("[Bundle with ").append(keySet.size()).append(" extras:");
            for (String key : keySet) {
                final Object value = bundle.get(key);
                builder.append(' ').append(key).append('=');
                builder.append((value instanceof Object[])
                        ? Arrays.toString((Objects[]) value) : value);
            }
            builder.append(']');
        }
        return builder;
    }

    /**
     * Appends {@code value} to the {@code builder} redacting its contents.
     */
    public static void appendRedacted(@NonNull StringBuilder builder,
            @Nullable CharSequence value) {
        if (value == null) {
            builder.append("null");
        } else {
            builder.append(value.length()).append("_chars");
        builder.append(getRedacted(value));
    }

    /**
     * Gets the redacted version of a value.
     */
    @NonNull
    public static String getRedacted(@Nullable CharSequence value) {
        return (value == null) ? "null" : value.length() + "_chars";
    }

    /**
+17 −8
Original line number Diff line number Diff line
@@ -47,7 +47,7 @@ import android.os.UserManager;
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;
import android.service.autofill.FillResponse;
@@ -75,7 +75,7 @@ import com.android.server.autofill.ui.AutoFillUI;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Map;
import java.util.Arrays;
import java.util.Random;

/**
@@ -628,7 +628,7 @@ final class AutofillManagerServiceImpl {
            if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
                mEventHistory.addEvent(
                        new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null,
                                null, null, null, null, null));
                                null, null, null, null, null, null));
            }
        }
    }
@@ -642,7 +642,7 @@ final class AutofillManagerServiceImpl {
            if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
                mEventHistory.addEvent(
                        new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
                                clientState, null, null, null, null, null, null, null));
                                clientState, null, null, null, null, null, null, null, null));
            }
        }
    }
@@ -654,7 +654,7 @@ final class AutofillManagerServiceImpl {
        synchronized (mLock) {
            if (isValidEventLocked("logSaveShown()", sessionId)) {
                mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
                        null, null, null, null, null, null));
                        null, null, null, null, null, null, null));
            }
        }
    }
@@ -668,7 +668,7 @@ final class AutofillManagerServiceImpl {
            if (isValidEventLocked("logDatasetSelected()", sessionId)) {
                mEventHistory.addEvent(
                        new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
                                null, null, null, null, null, null));
                                null, null, null, null, null, null, null));
            }
        }
    }
@@ -683,15 +683,24 @@ final class AutofillManagerServiceImpl {
            @Nullable ArrayList<String> changedDatasetIds,
            @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
            @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
            @Nullable Map<AutofillId, FieldClassification> fieldsClassification) {
            @NonNull ArrayList<AutofillId> detectedFieldIdsList,
            @NonNull ArrayList<Match> detectedMatchesList) {

        synchronized (mLock) {
            if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
                AutofillId[] detectedFieldsIds = null;
                Match[] detectedMatches = null;
                if (!detectedFieldIdsList.isEmpty()) {
                    detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()];
                    detectedFieldIdsList.toArray(detectedFieldsIds);
                    detectedMatches = new Match[detectedMatchesList.size()];
                    detectedMatchesList.toArray(detectedMatches);
                }
                mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
                        clientState, selectedDatasets, ignoredDatasets,
                        changedFieldIds, changedDatasetIds,
                        manuallyFilledFieldIds, manuallyFilledDatasetIds,
                        fieldsClassification));
                        detectedFieldsIds, detectedMatches));
            }
        }
    }
Loading