Refactored field detection mechanism to support multiple fields.

Now instead of using FillResponse.setFieldDetection() with an object that
contains both the autofill ids an the user data, service must:
- Set global mapping through AutofillService.setUserData()
- Pass just the autofill ids to FillResponse.setFieldClassificationIds()

This CL is purely a refactoring CL - although the new API supports multiple
values and multiple fields, the implementation is still hardcode for one
value and one field (as before).

Test: atest CtsAutoFillServiceTestCases:FieldsClassificationTest
Test: atest CtsAutoFillServiceTestCases:UserDataTest
Test: atest CtsAutoFillServiceTestCases:FillResponseTest

Bug: 68045531

Change-Id: I8d846d817dfcde3a4db7abff798bb2250e1c4e7b
diff --git a/api/test-current.txt b/api/test-current.txt
index b181538..de0945b 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -457,19 +457,12 @@
     method public void apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int) throws java.lang.Exception;
   }
 
-  public final class FieldsDetection implements android.os.Parcelable {
-    ctor public FieldsDetection(android.view.autofill.AutofillId, java.lang.String, java.lang.String);
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.autofill.FieldsDetection> CREATOR;
-  }
-
   public static final class FillEventHistory.Event {
-    method public java.util.Map<java.lang.String, java.lang.Integer> getDetectedFields();
+    method public java.util.Map<java.lang.String, java.lang.Integer> getFieldsClassification();
   }
 
   public static final class FillResponse.Builder {
-    method public android.service.autofill.FillResponse.Builder setFieldsDetection(android.service.autofill.FieldsDetection);
+    method public android.service.autofill.FillResponse.Builder setFieldClassificationIds(android.view.autofill.AutofillId...);
   }
 
   public final class ImageTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation {
@@ -501,6 +494,22 @@
     method public android.view.autofill.AutofillValue sanitize(android.view.autofill.AutofillValue);
   }
 
+  public final class UserData implements android.os.Parcelable {
+    method public int describeContents();
+    method public static int getMaxFieldClassificationIdsSize();
+    method public static int getMaxUserDataSize();
+    method public static int getMaxValueLength();
+    method public static int getMinValueLength();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.autofill.UserData> CREATOR;
+  }
+
+  public static final class UserData.Builder {
+    ctor public UserData.Builder(java.lang.String, java.lang.String);
+    method public android.service.autofill.UserData.Builder add(java.lang.String, java.lang.String);
+    method public android.service.autofill.UserData build();
+  }
+
   public abstract interface ValueFinder {
     method public abstract java.lang.String findByAutofillId(android.view.autofill.AutofillId);
   }
@@ -983,6 +992,11 @@
     ctor public AutofillId(int);
   }
 
+  public final class AutofillManager {
+    method public android.service.autofill.UserData getUserData();
+    method public void setUserData(android.service.autofill.UserData);
+  }
+
 }
 
 package android.widget {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1e0948a..775b822 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5333,6 +5333,42 @@
         public static final String AUTOFILL_FEATURE_FIELD_DETECTION = "autofill_field_detection";
 
         /**
+         * Experimental autofill feature.
+         *
+         * <p>TODO(b/67867469): document (or remove) once feature is finished
+         * @hide
+         */
+        public static final String AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE =
+                "autofill_user_data_max_user_data_size";
+
+        /**
+         * Experimental autofill feature.
+         *
+         * <p>TODO(b/67867469): document (or remove) once feature is finished
+         * @hide
+         */
+        public static final String AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE =
+                "autofill_user_data_max_field_classification_size";
+
+        /**
+         * Experimental autofill feature.
+         *
+         * <p>TODO(b/67867469): document (or remove) once feature is finished
+         * @hide
+         */
+        public static final String AUTOFILL_USER_DATA_MAX_VALUE_LENGTH =
+                "autofill_user_data_max_value_length";
+
+        /**
+         * Experimental autofill feature.
+         *
+         * <p>TODO(b/67867469): document (or remove) once feature is finished
+         * @hide
+         */
+        public static final String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH =
+                "autofill_user_data_min_value_length";
+
+        /**
          * @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead
          */
         @Deprecated
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index cd362c7..1afa8b3 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -440,7 +440,6 @@
  *  save(username, password);
  * </pre>
  *
- *
  * <a name="Privacy"></a>
  * <h3>Privacy</h3>
  *
@@ -453,6 +452,13 @@
  * <p>Because this data could contain PII (Personally Identifiable Information, such as username or
  * email address), the service should only use it locally (i.e., in the app's process) for
  * heuristics purposes, but it should not be sent to external servers.
+ *
+ * <a name="FieldsClassification"></a>
+ * <h3>Metrics and fields classification</h3
+ *
+ * <p>TODO(b/67867469): document it or remove this section; in particular, document the relationship
+ * between set/getUserData(), FillResponse.setFieldClassificationIds(), and
+ * FillEventHistory.getFieldsClassification.
  */
 public abstract class AutofillService extends Service {
     private static final String TAG = "AutofillService";
diff --git a/core/java/android/service/autofill/FieldsDetection.java b/core/java/android/service/autofill/FieldsDetection.java
deleted file mode 100644
index 550ecf6..0000000
--- a/core/java/android/service/autofill/FieldsDetection.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2017 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.service.autofill;
-
-import android.annotation.TestApi;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.autofill.AutofillId;
-
-/**
- * Class by service to improve autofillable fields detection by tracking the meaning of fields
- * manually edited by the user (when they match values provided by the service).
- *
- * TODO(b/67867469):
- *  - proper javadoc
- *  - unhide / remove testApi
- *  - add FieldsDetection management so service can set it just once and reference it in further
- *    calls to improve performance (and also API to refresh it)
- *  - rename to FieldsDetectionInfo or FieldClassification? (same for CTS tests)
- *  - add FieldsDetectionUnitTest once API is well-defined
- * @hide
- */
-@TestApi
-public final class FieldsDetection implements Parcelable {
-
-    private final AutofillId mFieldId;
-    private final String mRemoteId;
-    private final String mValue;
-
-    /**
-     * Creates a field detection for just one field / value pair.
-     *
-     * @param fieldId autofill id of the field in the screen.
-     * @param remoteId id used by the service to identify the field later.
-     * @param value field value known to the service.
-     *
-     * TODO(b/67867469):
-     *  - proper javadoc
-     *  - change signature to allow more fields / values / match methods
-     *    - might also need to use a builder, where the constructor is the id for the fieldsdetector
-     *    - might need id for values as well
-     *  - add @NonNull / check it / add unit tests
-     *  - make 'value' input more generic so it can accept distance-based match and other matches
-     *  - throw exception if field value is less than X characters (somewhere between 7-10)
-     *  - make sure to limit total number of fields to around 10 or so
-     *  - use AutofillValue instead of String (so it can compare dates, for example)
-     */
-    public FieldsDetection(AutofillId fieldId, String remoteId, String value) {
-        mFieldId = fieldId;
-        mRemoteId = remoteId;
-        mValue = value;
-    }
-
-    /** @hide */
-    public AutofillId getFieldId() {
-        return mFieldId;
-    }
-
-    /** @hide */
-    public String getRemoteId() {
-        return mRemoteId;
-    }
-
-    /** @hide */
-    public String getValue() {
-        return mValue;
-    }
-
-    /////////////////////////////////////
-    // Object "contract" methods. //
-    /////////////////////////////////////
-    @Override
-    public String toString() {
-        // Cannot disclose remoteId or value because they could contain PII
-        return new StringBuilder("FieldsDetection: [field=").append(mFieldId)
-                .append(", remoteId_length=").append(mRemoteId.length())
-                .append(", value_length=").append(mValue.length())
-                .append("]").toString();
-    }
-
-    /////////////////////////////////////
-    // Parcelable "contract" methods. //
-    /////////////////////////////////////
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeParcelable(mFieldId, flags);
-        parcel.writeString(mRemoteId);
-        parcel.writeString(mValue);
-    }
-
-    public static final Parcelable.Creator<FieldsDetection> CREATOR =
-            new Parcelable.Creator<FieldsDetection>() {
-        @Override
-        public FieldsDetection createFromParcel(Parcel parcel) {
-            // TODO(b/67867469): remove comment below if it does not use a builder at the end
-            // Always go through the builder to ensure the data ingested by
-            // the system obeys the contract of the builder to avoid attacks
-            // using specially crafted parcels.
-            return new FieldsDetection(parcel.readParcelable(null), parcel.readString(),
-                    parcel.readString());
-        }
-
-        @Override
-        public FieldsDetection[] newArray(int size) {
-            return new FieldsDetection[size];
-        }
-    };
-}
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 736d9ef..eedb972 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -58,11 +58,6 @@
  */
 public final class FillEventHistory implements Parcelable {
     /**
-     * Not in parcel. The UID of the {@link AutofillService} that created the {@link FillResponse}.
-     */
-    private final int mServiceUid;
-
-    /**
      * Not in parcel. The ID of the autofill session that created the {@link FillResponse}.
      */
     private final int mSessionId;
@@ -70,17 +65,6 @@
     @Nullable private final Bundle mClientState;
     @Nullable List<Event> mEvents;
 
-    /**
-     * Gets the UID of the {@link AutofillService} that created the {@link FillResponse}.
-     *
-     * @return The UID of the {@link AutofillService}
-     *
-     * @hide
-     */
-    public int getServiceUid() {
-        return mServiceUid;
-    }
-
     /** @hide */
     public int getSessionId() {
         return mSessionId;
@@ -123,9 +107,8 @@
     /**
      * @hide
      */
-    public FillEventHistory(int serviceUid, int sessionId, @Nullable Bundle clientState) {
+    public FillEventHistory(int sessionId, @Nullable Bundle clientState) {
         mClientState = clientState;
-        mServiceUid = serviceUid;
         mSessionId = sessionId;
     }
 
@@ -364,16 +347,17 @@
         }
 
         /**
-         * Gets the results of the last {@link FieldsDetection} request.
+         * Gets the results of the last fields classification request.
          *
          * @return map of edit-distance match ({@code 0} means full match,
-         * {@code 1} means 1 character different, etc...) by remote id (as set in the
-         * {@link FieldsDetection} constructor), or {@code null} if none of the user-input values
+         * {@code 1} means 1 character different, etc...) by remote id (as set on
+         * {@link UserData.Builder#add(String, android.view.autofill.AutofillValue)}),
+         * or {@code null} if none of the user-input values
          * matched the requested detection.
          *
          * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}, when the
-         * service requested {@link FillResponse.Builder#setFieldsDetection(FieldsDetection) fields
-         * detection}.
+         * service requested {@link FillResponse.Builder#setFieldClassificationIds(AutofillId...)
+         * fields detection}.
          *
          * TODO(b/67867469):
          *  - improve javadoc
@@ -382,11 +366,12 @@
          *  - unhide
          *  - unhide / remove testApi
          *  - add @NonNull / check it / add unit tests
+         *  - add link to AutofillService #FieldsClassification anchor
          *
          * @hide
          */
         @TestApi
-        @NonNull public Map<String, Integer> getDetectedFields() {
+        @NonNull public Map<String, Integer> getFieldsClassification() {
             if (mDetectedRemoteId == null || mDetectedFieldScore == -1) {
                 return Collections.emptyMap();
             }
@@ -534,7 +519,7 @@
             new Parcelable.Creator<FillEventHistory>() {
                 @Override
                 public FillEventHistory createFromParcel(Parcel parcel) {
-                    FillEventHistory selection = new FillEventHistory(0, 0, parcel.readBundle());
+                    FillEventHistory selection = new FillEventHistory(0, parcel.readBundle());
 
                     final int numEvents = parcel.readInt();
                     for (int i = 0; i < numEvents; i++) {
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 84a0974..dff40ff 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -76,7 +76,7 @@
     private final @Nullable AutofillId[] mAuthenticationIds;
     private final @Nullable AutofillId[] mIgnoredIds;
     private final long mDisableDuration;
-    private final @Nullable FieldsDetection mFieldsDetection;
+    private final @Nullable AutofillId[] mFieldClassificationIds;
     private final int mFlags;
     private int mRequestId;
 
@@ -89,7 +89,7 @@
         mAuthenticationIds = builder.mAuthenticationIds;
         mIgnoredIds = builder.mIgnoredIds;
         mDisableDuration = builder.mDisableDuration;
-        mFieldsDetection = builder.mFieldsDetection;
+        mFieldClassificationIds = builder.mFieldClassificationIds;
         mFlags = builder.mFlags;
         mRequestId = INVALID_REQUEST_ID;
     }
@@ -135,8 +135,8 @@
     }
 
     /** @hide */
-    public @Nullable FieldsDetection getFieldsDetection() {
-        return mFieldsDetection;
+    public @Nullable AutofillId[] getFieldClassificationIds() {
+        return mFieldClassificationIds;
     }
 
     /** @hide */
@@ -175,7 +175,7 @@
         private AutofillId[] mAuthenticationIds;
         private AutofillId[] mIgnoredIds;
         private long mDisableDuration;
-        private FieldsDetection mFieldsDetection;
+        private AutofillId[] mFieldClassificationIds;
         private int mFlags;
         private boolean mDestroyed;
 
@@ -329,21 +329,29 @@
         }
 
         /**
+         * Sets which fields are used for <a href="#FieldsClassification">fields classification</a>
+         *
+         * @throws IllegalArgumentException is length of {@code ids} args is more than
+         * {@link UserData#getMaxFieldClassificationIdsSize()}.
+         * @throws IllegalStateException if {@link #build()} or {@link #disableAutofill(long)} was
+         * already called.
+         * @throws NullPointerException if {@code ids} or any element on it is {@code null}.
+         *
          * TODO(b/67867469):
-         *  - javadoc it
-         *  - javadoc how to check results
-         *  - unhide
+         *  - improve javadoc: explain relationship with UserData and how to check results
          *  - unhide / remove testApi
-         *  - throw exception (and document) if response has datasets or saveinfo
-         *  - throw exception (and document) if id on fieldsDetection is ignored
+         *  - implement multiple ids
          *
          * @hide
          */
         @TestApi
-        public Builder setFieldsDetection(@NonNull FieldsDetection fieldsDetection) {
+        public Builder setFieldClassificationIds(@NonNull AutofillId... ids) {
             throwIfDestroyed();
             throwIfDisableAutofillCalled();
-            mFieldsDetection = Preconditions.checkNotNull(fieldsDetection);
+            Preconditions.checkArrayElementsNotNull(ids, "ids");
+            Preconditions.checkArgumentInRange(ids.length, 1,
+                    UserData.getMaxFieldClassificationIdsSize(), "ids length");
+            mFieldClassificationIds = ids;
             return this;
         }
 
@@ -391,16 +399,17 @@
          * @throws IllegalArgumentException if {@code duration} is not a positive number.
          * @throws IllegalStateException if either {@link #addDataset(Dataset)},
          *       {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
-         *       {@link #setSaveInfo(SaveInfo)}, or {@link #setClientState(Bundle)}
-         *       was already called.
+         *       {@link #setSaveInfo(SaveInfo)}, {@link #setClientState(Bundle)}, or
+         *       {link #setFieldClassificationIds(AutofillId...)} was already called.
          */
+        // TODO(b/67867469): add @ to {link setFieldClassificationIds} once it's public
         public Builder disableAutofill(long duration) {
             throwIfDestroyed();
             if (duration <= 0) {
                 throw new IllegalArgumentException("duration must be greater than 0");
             }
             if (mAuthentication != null || mDatasets != null || mSaveInfo != null
-                    || mFieldsDetection != null || mClientState != null) {
+                    || mFieldClassificationIds != null || mClientState != null) {
                 throw new IllegalStateException("disableAutofill() must be the only method called");
             }
 
@@ -417,15 +426,18 @@
          *   <li>No call was made to {@link #addDataset(Dataset)},
          *       {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
          *       {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)},
-         *       or {@link #setClientState(Bundle)}.
+         *       {@link #setClientState(Bundle)},
+         *       or {link #setFieldClassificationIds(AutofillId...)}.
          * </ol>
          *
          * @return A built response.
          */
+        // TODO(b/67867469): add @ to {link setFieldClassificationIds} once it's public
         public FillResponse build() {
             throwIfDestroyed();
             if (mAuthentication == null && mDatasets == null && mSaveInfo == null
-                    && mDisableDuration == 0 && mFieldsDetection == null && mClientState == null) {
+                    && mDisableDuration == 0 && mFieldClassificationIds == null
+                    && mClientState == null) {
                 throw new IllegalStateException("need to provide: at least one DataSet, or a "
                         + "SaveInfo, or an authentication with a presentation, "
                         + "or a FieldsDetection, or a client state, or disable autofill");
@@ -466,7 +478,8 @@
                 .append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
                 .append(", disableDuration=").append(mDisableDuration)
                 .append(", flags=").append(mFlags)
-                .append(", fieldDetection=").append(mFieldsDetection)
+                .append(", fieldClassificationIds=")
+                    .append(Arrays.toString(mFieldClassificationIds))
                 .append("]")
                 .toString();
     }
@@ -490,7 +503,7 @@
         parcel.writeParcelable(mPresentation, flags);
         parcel.writeParcelableArray(mIgnoredIds, flags);
         parcel.writeLong(mDisableDuration);
-        parcel.writeParcelable(mFieldsDetection, flags);
+        parcel.writeParcelableArray(mFieldClassificationIds, flags);
         parcel.writeInt(mFlags);
         parcel.writeInt(mRequestId);
     }
@@ -526,9 +539,10 @@
             if (disableDuration > 0) {
                 builder.disableAutofill(disableDuration);
             }
-            final FieldsDetection fieldsDetection = parcel.readParcelable(null);
-            if (fieldsDetection != null) {
-                builder.setFieldsDetection(fieldsDetection);
+            final AutofillId[] fieldClassifactionIds =
+                    parcel.readParcelableArray(null, AutofillId.class);
+            if (fieldClassifactionIds != null) {
+                builder.setFieldClassificationIds(fieldClassifactionIds);
             }
             builder.setFlags(parcel.readInt());
 
diff --git a/core/java/android/service/autofill/UserData.aidl b/core/java/android/service/autofill/UserData.aidl
new file mode 100644
index 0000000..76016de
--- /dev/null
+++ b/core/java/android/service/autofill/UserData.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2017, 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.service.autofill;
+
+parcelable UserData;
+parcelable UserData.Constraints;
diff --git a/core/java/android/service/autofill/UserData.java b/core/java/android/service/autofill/UserData.java
new file mode 100644
index 0000000..16d8d4a
--- /dev/null
+++ b/core/java/android/service/autofill/UserData.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2017 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.service.autofill;
+
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
+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.Parcel;
+import android.os.Parcelable;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.autofill.Helper;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Class used by service to improve autofillable fields detection by tracking the meaning of fields
+ * manually edited by the user (when they match values provided by the service).
+ *
+ * TODO(b/67867469):
+ *  - improve javadoc / add link to section on AutofillService
+ *  - unhide / remove testApi
+ * @hide
+ */
+@TestApi
+public final class UserData implements Parcelable {
+
+    private static final String TAG = "UserData";
+
+    private static final int DEFAULT_MAX_USER_DATA_SIZE = 10;
+    private static final int DEFAULT_MAX_FIELD_CLASSIFICATION_IDS_SIZE = 10;
+    private static final int DEFAULT_MIN_VALUE_LENGTH = 5;
+    private static final int DEFAULT_MAX_VALUE_LENGTH = 100;
+
+    private final String[] mRemoteIds;
+    private final String[] mValues;
+
+    private UserData(Builder builder) {
+        mRemoteIds = new String[builder.mRemoteIds.size()];
+        builder.mRemoteIds.toArray(mRemoteIds);
+        mValues = new String[builder.mValues.size()];
+        builder.mValues.toArray(mValues);
+    }
+
+    /** @hide */
+    public String[] getRemoteIds() {
+        return mRemoteIds;
+    }
+
+    /** @hide */
+    public String[] getValues() {
+        return mValues;
+    }
+
+    /** @hide */
+    public void dump(String prefix, PrintWriter pw) {
+        // Cannot disclose remote ids because they could contain PII
+        pw.print(prefix); pw.print("Remote ids size: "); pw.println(mRemoteIds.length);
+        for (int i = 0; i < mValues.length; i++) {
+            pw.print(prefix); pw.print(prefix); pw.print(i); pw.print(": "); pw.println(mValues[i]);
+        }
+    }
+
+    /** @hide */
+    public static void dumpConstraints(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("maxUserDataSize: "); pw.println(getMaxUserDataSize());
+        pw.print(prefix); pw.print("maxFieldClassificationIdsSize: ");
+        pw.println(getMaxFieldClassificationIdsSize());
+        pw.print(prefix); pw.print("minValueLength: "); pw.println(getMinValueLength());
+        pw.print(prefix); pw.print("maxValueLength: "); pw.println(getMaxValueLength());
+    }
+
+    /**
+     * A builder for {@link UserData} objects.
+     *
+     * TODO(b/67867469): unhide / remove testApi
+     *
+     * @hide
+     */
+    @TestApi
+    public static final class Builder {
+        private final ArraySet<String> mRemoteIds;
+        private final ArrayList<String> mValues;
+        private boolean mDestroyed;
+
+        /**
+         * Creates a new builder for the user data used for <a href="#FieldsClassification">fields
+         * classification</a>.
+         *
+         * @throws IllegalArgumentException if {@code remoteId} or {@code value} are empty or if the
+         * length of {@code value} is lower than {@link UserData#getMinValueLength()}
+         * or higher than {@link UserData#getMaxValueLength()}.
+         */
+        public Builder(@NonNull String remoteId, @NonNull String value) {
+            checkValidRemoteId(remoteId);
+            checkValidValue(value);
+            final int capacity = getMaxUserDataSize();
+            mRemoteIds = new ArraySet<>(capacity);
+            mValues = new ArrayList<>(capacity);
+            mRemoteIds.add(remoteId);
+            mValues.add(value);
+        }
+
+        /**
+         * Adds a new value for user data.
+         *
+         * @param remoteId unique string used to identify the user data.
+         * @param value value of the user data.
+         *
+         * @throws IllegalStateException if {@link #build()} or
+         * {@link #add(String, String)} with the same {@code remoteId} has already
+         * been called, or if the number of values add (i.e., calls made to this method plus
+         * constructor) is more than {@link UserData#getMaxUserDataSize()}.
+         *
+         * @throws IllegalArgumentException if {@code remoteId} or {@code value} are empty or if the
+         * length of {@code value} is lower than {@link UserData#getMinValueLength()}
+         * or higher than {@link UserData#getMaxValueLength()}.
+         */
+        public Builder add(@NonNull String remoteId, @NonNull String value) {
+            throwIfDestroyed();
+            checkValidRemoteId(remoteId);
+            checkValidValue(value);
+
+            Preconditions.checkState(!mRemoteIds.contains(remoteId),
+                    // Don't include remoteId on message because it could contain PII
+                    "already has entry with same remoteId");
+            Preconditions.checkState(mRemoteIds.size() < getMaxUserDataSize(),
+                    "already added " + mRemoteIds.size() + " elements");
+            mRemoteIds.add(remoteId);
+            mValues.add(value);
+            return this;
+        }
+
+        private void checkValidRemoteId(@Nullable String remoteId) {
+            Preconditions.checkNotNull(remoteId);
+            Preconditions.checkArgument(!remoteId.isEmpty(), "remoteId cannot be empty");
+        }
+
+        private void checkValidValue(@Nullable String value) {
+            Preconditions.checkNotNull(value);
+            final int length = value.length();
+            Preconditions.checkArgumentInRange(length, getMinValueLength(),
+                    getMaxValueLength(), "value length (" + length + ")");
+        }
+
+        /**
+         * Creates a new {@link UserData} instance.
+         *
+         * <p>You should not interact with this builder once this method is called.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         *
+         * @return The built dataset.
+         */
+        public UserData build() {
+            throwIfDestroyed();
+            mDestroyed = true;
+            return new UserData(this);
+        }
+
+        private void throwIfDestroyed() {
+            if (mDestroyed) {
+                throw new IllegalStateException("Already called #build()");
+            }
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        // Cannot disclose keys or values because they could contain PII
+        final StringBuilder builder = new StringBuilder("UserData: [remoteIds=");
+        Helper.appendRedacted(builder, mRemoteIds);
+        builder.append(", values=");
+        Helper.appendRedacted(builder, mValues);
+        return builder.append("]").toString();
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeStringArray(mRemoteIds);
+        parcel.writeStringArray(mValues);
+    }
+
+    public static final Parcelable.Creator<UserData> CREATOR =
+            new Parcelable.Creator<UserData>() {
+        @Override
+        public UserData createFromParcel(Parcel parcel) {
+            // Always go through the builder to ensure the data ingested by
+            // the system obeys the contract of the builder to avoid attacks
+            // using specially crafted parcels.
+            final String[] remoteIds = parcel.readStringArray();
+            final String[] values = parcel.readStringArray();
+            final Builder builder = new Builder(remoteIds[0], values[0]);
+            for (int i = 1; i < remoteIds.length; i++) {
+                builder.add(remoteIds[i], values[i]);
+            }
+            return builder.build();
+        }
+
+        @Override
+        public UserData[] newArray(int size) {
+            return new UserData[size];
+        }
+    };
+
+    /**
+     * Gets the maximum number of values that can be added to a {@link UserData}.
+     */
+    public static int getMaxUserDataSize() {
+        return getInt(AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, DEFAULT_MAX_USER_DATA_SIZE);
+    }
+
+    /**
+     * Gets the maximum number of ids that can be passed to {@link
+     * FillResponse.Builder#setFieldClassificationIds(android.view.autofill.AutofillId...)}.
+     */
+    public static int getMaxFieldClassificationIdsSize() {
+        return getInt(AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE,
+            DEFAULT_MAX_FIELD_CLASSIFICATION_IDS_SIZE);
+    }
+
+    /**
+     * Gets the minimum length of values passed to {@link Builder#Builder(String, String)}.
+     */
+    public static int getMinValueLength() {
+        return getInt(AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, DEFAULT_MIN_VALUE_LENGTH);
+    }
+
+    /**
+     * Gets the maximum length of values passed to {@link Builder#Builder(String, String)}.
+     */
+    public static int getMaxValueLength() {
+        return getInt(AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, DEFAULT_MAX_VALUE_LENGTH);
+    }
+
+    private static int getInt(String settings, int defaultValue) {
+        ContentResolver cr = null;
+        final ActivityThread at = ActivityThread.currentActivityThread();
+        if (at != null) {
+            cr = at.getApplication().getContentResolver();
+        }
+
+        if (cr == null) {
+            Log.w(TAG, "Could not read from " + settings + "; hardcoding " + defaultValue);
+            return defaultValue;
+        }
+        return Settings.Secure.getInt(cr, settings, defaultValue);
+    }
+}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 547e0db..9a99e53 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -36,6 +37,7 @@
 import android.os.RemoteException;
 import android.service.autofill.AutofillService;
 import android.service.autofill.FillEventHistory;
+import android.service.autofill.UserData;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -1007,6 +1009,54 @@
     }
 
     /**
+     * Gets the user data used for <a href="#FieldsClassification">fields classification</a>.
+     *
+     * <p><b>Note:</b> This method should only be called by an app providing an autofill service.
+     *
+     * TODO(b/67867469):
+     *  - proper javadoc
+     *  - unhide / remove testApi
+     *
+     * @return value previously set by {@link #setUserData(UserData)} or {@code null} if it was
+     * reset or if the caller currently does not have an enabled autofill service for the user.
+     *
+     * @hide
+     */
+    @TestApi
+    @Nullable public UserData getUserData() {
+        try {
+            return mService.getUserData();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return null;
+        }
+    }
+
+    /**
+     * Sets the user data used for <a href="#FieldsClassification">fields classification</a>.
+     *
+     * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
+     * and it's ignored if the caller currently doesn't have an enabled autofill service for
+     * the user.
+     *
+     * TODO(b/67867469):
+     *  - proper javadoc
+     *  - unhide / remove testApi
+     *  - add unit tests:
+     *    - call set / get / verify
+     *
+     * @hide
+     */
+    @TestApi
+    public void setUserData(@Nullable UserData userData) {
+        try {
+            mService.setUserData(userData);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns {@code true} if autofill is supported by the current device and
      * is supported for this user.
      *
diff --git a/core/java/android/view/autofill/AutofillValue.java b/core/java/android/view/autofill/AutofillValue.java
index 3beae11..8e649de 100644
--- a/core/java/android/view/autofill/AutofillValue.java
+++ b/core/java/android/view/autofill/AutofillValue.java
@@ -177,7 +177,7 @@
                 .append("[type=").append(mType)
                 .append(", value=");
         if (isText()) {
-            string.append(((CharSequence) mValue).length()).append("_chars");
+            Helper.appendRedacted(string, (CharSequence) mValue);
         } else {
             string.append(mValue);
         }
diff --git a/core/java/android/view/autofill/Helper.java b/core/java/android/view/autofill/Helper.java
index 829e7f3..b95704a 100644
--- a/core/java/android/view/autofill/Helper.java
+++ b/core/java/android/view/autofill/Helper.java
@@ -16,6 +16,8 @@
 
 package android.view.autofill;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Bundle;
 
 import java.util.Arrays;
@@ -50,6 +52,35 @@
         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");
+        }
+    }
+
+    /**
+     * Appends {@code values} to the {@code builder} redacting its contents.
+     */
+    public static void appendRedacted(@NonNull StringBuilder builder, @Nullable String[] values) {
+        if (values == null) {
+            builder.append("N/A");
+            return;
+        }
+        builder.append("[");
+        for (String value : values) {
+            builder.append(" '");
+            appendRedacted(builder, value);
+            builder.append("'");
+        }
+        builder.append(" ]");
+    }
+
     private Helper() {
         throw new UnsupportedOperationException("contains static members only");
     }
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index d6db3fe..7d6a19f 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -21,6 +21,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.service.autofill.FillEventHistory;
+import android.service.autofill.UserData;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutoFillManagerClient;
@@ -53,4 +54,6 @@
     boolean isServiceSupported(int userId);
     boolean isServiceEnabled(int userId, String packageName);
     void onPendingSaveUi(int operation, IBinder token);
+    UserData getUserData();
+    void setUserData(in UserData userData);
 }
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 4ce6029..2e1c0a1 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -419,7 +419,13 @@
     private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
              newHashSet(
                  Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+                 // TODO(b/67867469): Move autofill settings below to
+                 // BACKUP_BLACKLISTED_SYSTEM_SETTINGS once feature is moved out of experimental
                  Settings.Secure.AUTOFILL_FEATURE_FIELD_DETECTION,
+                 Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE,
+                 Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE,
+                 Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH,
+                 Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH,
                  Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
                  Settings.Secure.ALWAYS_ON_VPN_APP,
                  Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 23e4f50..0291276 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -52,6 +52,7 @@
 import android.os.UserManagerInternal;
 import android.provider.Settings;
 import android.service.autofill.FillEventHistory;
+import android.service.autofill.UserData;
 import android.util.LocalLog;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -581,6 +582,34 @@
         }
 
         @Override
+        public UserData getUserData() throws RemoteException {
+            UserHandle user = getCallingUserHandle();
+            int uid = getCallingUid();
+
+            synchronized (mLock) {
+                AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+                if (service != null) {
+                    return service.getUserData(uid);
+                }
+            }
+
+            return null;
+        }
+
+        @Override
+        public void setUserData(UserData userData) throws RemoteException {
+            UserHandle user = getCallingUserHandle();
+            int uid = getCallingUid();
+
+            synchronized (mLock) {
+                AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+                if (service != null) {
+                    service.setUserData(uid, userData);
+                }
+            }
+        }
+
+        @Override
         public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
                 throws RemoteException {
             activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
@@ -723,6 +752,7 @@
             }
 
             boolean oldDebug = sDebug;
+            final String prefix = "  ";
             try {
                 synchronized (mLock) {
                     oldDebug = sDebug;
@@ -731,6 +761,7 @@
                     pw.print("Verbose mode: "); pw.println(sVerbose);
                     pw.print("Disabled users: "); pw.println(mDisabledUsers);
                     pw.print("Max partitions per session: "); pw.println(sPartitionMaxCount);
+                    pw.println("User data constraints: "); UserData.dumpConstraints(prefix, pw);
                     final int size = mServicesCache.size();
                     pw.print("Cached services: ");
                     if (size == 0) {
@@ -740,7 +771,7 @@
                         for (int i = 0; i < size; i++) {
                             pw.print("\nService at index "); pw.println(i);
                             final AutofillManagerServiceImpl impl = mServicesCache.valueAt(i);
-                            impl.dumpLocked("  ", pw);
+                            impl.dumpLocked(prefix, pw);
                         }
                     }
                     mUi.dump(pw);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 21e2722..8b6dc20 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -51,6 +51,7 @@
 import android.service.autofill.FillEventHistory.Event;
 import android.service.autofill.FillResponse;
 import android.service.autofill.IAutoFillService;
+import android.service.autofill.UserData;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -121,6 +122,11 @@
     private boolean mDisabled;
 
     /**
+     * Data used for field classification.
+     */
+    private UserData mUserData;
+
+    /**
      * Caches whether the setup completed for the current user.
      */
     @GuardedBy("mLock")
@@ -183,6 +189,14 @@
         }
     }
 
+    private int getServiceUidLocked() {
+        if (mInfo == null) {
+            Slog.w(TAG,  "getServiceUidLocked(): no mInfo");
+            return -1;
+        }
+        return mInfo.getServiceInfo().applicationInfo.uid;
+    }
+
     @Nullable
     String getServicePackageName() {
         final ComponentName serviceComponent = getServiceComponentName();
@@ -574,9 +588,9 @@
      * Initializes the last fill selection after an autofill service returned a new
      * {@link FillResponse}.
      */
-    void setLastResponse(int serviceUid, int sessionId, @NonNull FillResponse response) {
+    void setLastResponse(int sessionId, @NonNull FillResponse response) {
         synchronized (mLock) {
-            mEventHistory = new FillEventHistory(serviceUid, sessionId, response.getClientState());
+            mEventHistory = new FillEventHistory(sessionId, response.getClientState());
         }
     }
 
@@ -688,18 +702,54 @@
      */
     FillEventHistory getFillEventHistory(int callingUid) {
         synchronized (mLock) {
-            if (mEventHistory != null && mEventHistory.getServiceUid() == callingUid) {
+            if (mEventHistory != null
+                    && isCalledByServiceLocked("getFillEventHistory", callingUid)) {
                 return mEventHistory;
             }
         }
-
         return null;
     }
 
+    // Called by Session - does not need to check uid
+    UserData getUserData() {
+        synchronized (mLock) {
+            return mUserData;
+        }
+    }
+
+    // Called by AutofillManager
+    UserData getUserData(int callingUid) {
+        synchronized (mLock) {
+            if (isCalledByServiceLocked("getUserData", callingUid)) {
+                return mUserData;
+            }
+        }
+        return null;
+    }
+
+    // Called by AutofillManager
+    void setUserData(int callingUid, UserData userData) {
+        synchronized (mLock) {
+            if (isCalledByServiceLocked("setUserData", callingUid)) {
+                mUserData = userData;
+            }
+        }
+    }
+
+    private boolean isCalledByServiceLocked(String methodName, int callingUid) {
+        if (getServiceUidLocked() != callingUid) {
+            Slog.w(TAG, methodName + "() called by UID " + callingUid
+                    + ", but service UID is " + getServiceUidLocked());
+            return false;
+        }
+        return true;
+    }
+
     void dumpLocked(String prefix, PrintWriter pw) {
         final String prefix2 = prefix + "  ";
 
         pw.print(prefix); pw.print("User: "); pw.println(mUserId);
+        pw.print(prefix); pw.print("UID: "); pw.println(getServiceUidLocked());
         pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null
                 ? mInfo.getServiceInfo().getComponentName() : null);
         pw.print(prefix); pw.print("Component from settings: ");
@@ -762,8 +812,13 @@
             }
         }
 
-        pw.print(prefix); pw.println("Clients");
-        mClients.dump(pw, prefix2);
+        pw.print(prefix); pw.print("Clients: ");
+        if (mClients == null) {
+            pw.println("N/A");
+        } else {
+            pw.println();
+            mClients.dump(pw, prefix2);
+        }
 
         if (mEventHistory == null || mEventHistory.getEvents() == null
                 || mEventHistory.getEvents().size() == 0) {
@@ -779,6 +834,14 @@
                         + event.getDatasetId());
             }
         }
+
+        pw.print(prefix); pw.print("User data: ");
+        if (mUserData == null) {
+            pw.println("N/A");
+        } else {
+            pw.println();
+            mUserData.dump(prefix2, pw);
+        }
     }
 
     void destroySessionsLocked() {
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 831c488..aea9ad0 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -97,7 +97,7 @@
     private PendingRequest mPendingRequest;
 
     public interface FillServiceCallbacks {
-        void onFillRequestSuccess(int requestFlags, @Nullable FillResponse response, int serviceUid,
+        void onFillRequestSuccess(int requestFlags, @Nullable FillResponse response,
                 @NonNull String servicePackageName);
         void onFillRequestFailure(@Nullable CharSequence message,
                 @NonNull String servicePackageName);
@@ -281,11 +281,11 @@
         mContext.unbindService(mServiceConnection);
     }
 
-    private void dispatchOnFillRequestSuccess(PendingRequest pendingRequest,
-            int callingUid, int requestFlags, FillResponse response) {
+    private void dispatchOnFillRequestSuccess(PendingRequest pendingRequest, int requestFlags,
+            FillResponse response) {
         mHandler.getHandler().post(() -> {
             if (handleResponseCallbackCommon(pendingRequest)) {
-                mCallbacks.onFillRequestSuccess(requestFlags, response, callingUid,
+                mCallbacks.onFillRequestSuccess(requestFlags, response,
                         mComponentName.getPackageName());
             }
         });
@@ -546,7 +546,7 @@
                     final RemoteFillService remoteService = getService();
                     if (remoteService != null) {
                         remoteService.dispatchOnFillRequestSuccess(PendingFillRequest.this,
-                                getCallingUid(), request.getFlags(), response);
+                                request.getFlags(), response);
                     }
                 }
 
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 99b92b9..4a054f7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -55,7 +55,6 @@
 import android.os.SystemClock;
 import android.service.autofill.AutofillService;
 import android.service.autofill.Dataset;
-import android.service.autofill.FieldsDetection;
 import android.service.autofill.FillContext;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
@@ -63,6 +62,7 @@
 import android.service.autofill.InternalValidator;
 import android.service.autofill.SaveInfo;
 import android.service.autofill.SaveRequest;
+import android.service.autofill.UserData;
 import android.service.autofill.ValueFinder;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -480,7 +480,7 @@
     // FillServiceCallbacks
     @Override
     public void onFillRequestSuccess(int requestFlags, @Nullable FillResponse response,
-            int serviceUid, @NonNull String servicePackageName) {
+            @NonNull String servicePackageName) {
         synchronized (mLock) {
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
@@ -494,13 +494,13 @@
         }
 
         // TODO(b/67867469): remove once feature is finished
-        if (response.getFieldsDetection() != null && !mService.isFieldDetectionEnabled()) {
+        if (response.getFieldClassificationIds() != null && !mService.isFieldDetectionEnabled()) {
             Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
             processNullResponseLocked(requestFlags);
             return;
         }
 
-        mService.setLastResponse(serviceUid, id, response);
+        mService.setLastResponse(id, response);
 
         int sessionFinishedState = 0;
         final long disableDuration = response.getDisableDuration();
@@ -903,7 +903,7 @@
             final FillResponse response = mResponses.valueAt(i);
             final List<Dataset> datasets = response.getDatasets();
             if (datasets == null || datasets.isEmpty()) {
-                if (sVerbose) Slog.v(TAG,  "logContextCommitted() no datasets at " + i);
+                if (sVerbose) Slog.v(TAG, "logContextCommitted() no datasets at " + i);
             } else {
                 for (int j = 0; j < datasets.size(); j++) {
                     final Dataset dataset = datasets.get(j);
@@ -926,25 +926,27 @@
                 }
             }
         }
-        final FieldsDetection fieldsDetection = lastResponse.getFieldsDetection();
+        final AutofillId[] fieldClassificationIds = lastResponse.getFieldClassificationIds();
 
-        if (!hasAtLeastOneDataset && fieldsDetection == null) {
+        if (!hasAtLeastOneDataset && fieldClassificationIds == null) {
             if (sVerbose) {
                 Slog.v(TAG, "logContextCommittedLocked(): skipped (no datasets nor fields "
-                        + "detection)");
+                        + "classification ids)");
             }
             return;
         }
 
+        final UserData userData = mService.getUserData();
         final AutofillId detectableFieldId;
         final String detectableRemoteId;
         String detectedRemoteId = null;
-        if (fieldsDetection == null) {
+        if (userData == null) {
             detectableFieldId = null;
             detectableRemoteId = null;
         } else {
-            detectableFieldId = fieldsDetection.getFieldId();
-            detectableRemoteId = fieldsDetection.getRemoteId();
+            // TODO(b/67867469): hardcoded to just first entry on initial refactoring.
+            detectableFieldId = fieldClassificationIds[0];
+            detectableRemoteId = userData.getRemoteIds()[0];
         }
 
         int detectedFieldScore = -1;
@@ -1057,7 +1059,8 @@
                     if (detectableFieldId != null && detectableFieldId.equals(viewState.id)
                             && currentValue.isText() && currentValue.getTextValue() != null) {
                         final String actualValue = currentValue.getTextValue().toString();
-                        final String expectedValue = fieldsDetection.getValue();
+                        // TODO(b/67867469): hardcoded to just first entry on initial refactoring.
+                        final String expectedValue = userData.getValues()[0];
                         if (actualValue.equalsIgnoreCase(expectedValue)) {
                             detectedRemoteId = detectableRemoteId;
                             detectedFieldScore = 0;