Automatic sources dropoff on 2020-06-10 18:32:38.095721

The change is generated with prebuilt drop tool.

Change-Id: I24cbf6ba6db262a1ae1445db1427a08fee35b3b4
diff --git a/android/service/autofill/AutofillFieldClassificationService.java b/android/service/autofill/AutofillFieldClassificationService.java
new file mode 100644
index 0000000..28842a7
--- /dev/null
+++ b/android/service/autofill/AutofillFieldClassificationService.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.autofill.AutofillValue;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A service that calculates field classification scores.
+ *
+ * <p>A field classification score is a {@code float} representing how well an
+ * {@link AutofillValue} filled matches a expected value predicted by an autofill service
+ * &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
+ *
+ * <p>The exact score depends on the algorithm used to calculate it&mdash;the service must provide
+ * at least one default algorithm (which is used when the algorithm is not specified or is invalid),
+ * but it could provide more (in which case the algorithm name should be specified by the caller
+ * when calculating the scores).
+ *
+ * {@hide}
+ */
+@SystemApi
+@TestApi
+public abstract class AutofillFieldClassificationService extends Service {
+
+    private static final String TAG = "AutofillFieldClassificationService";
+
+    /**
+     * The {@link Intent} action that must be declared as handled by a service
+     * in its manifest for the system to recognize it as a quota providing service.
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.service.autofill.AutofillFieldClassificationService";
+
+    /**
+     * Manifest metadata key for the resource string containing the name of the default field
+     * classification algorithm.
+     */
+    public static final String SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM =
+            "android.autofill.field_classification.default_algorithm";
+    /**
+     * Manifest metadata key for the resource string array containing the names of all field
+     * classification algorithms provided by the service.
+     */
+    public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
+            "android.autofill.field_classification.available_algorithms";
+
+    /**
+     * Field classification algorithm that computes the edit distance between two Strings.
+     *
+     * <p>Service implementation must provide this algorithm.</p>
+     */
+    public static final String REQUIRED_ALGORITHM_EDIT_DISTANCE = "EDIT_DISTANCE";
+
+    /**
+     * Field classification algorithm that computes whether the last four digits between two
+     * Strings match exactly.
+     *
+     * <p>Service implementation must provide this algorithm.</p>
+     */
+    public static final String REQUIRED_ALGORITHM_EXACT_MATCH = "EXACT_MATCH";
+
+    /**
+     * Field classification algorithm that compares a credit card string to known last four digits.
+     *
+     * <p>Service implementation must provide this algorithm.</p>
+     */
+    public static final String REQUIRED_ALGORITHM_CREDIT_CARD = "CREDIT_CARD";
+
+    /** {@hide} **/
+    public static final String EXTRA_SCORES = "scores";
+
+    private AutofillFieldClassificationServiceWrapper mWrapper;
+
+    private void calculateScores(RemoteCallback callback, List<AutofillValue> actualValues,
+            String[] userDataValues, String[] categoryIds, String defaultAlgorithm,
+            Bundle defaultArgs, Map algorithms, Map args) {
+        final Bundle data = new Bundle();
+        final float[][] scores = onCalculateScores(actualValues, Arrays.asList(userDataValues),
+                Arrays.asList(categoryIds), defaultAlgorithm, defaultArgs, algorithms, args);
+        if (scores != null) {
+            data.putParcelable(EXTRA_SCORES, new Scores(scores));
+        }
+        callback.sendResult(data);
+    }
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
+
+    /** @hide */
+    @SystemApi
+    @TestApi
+    public AutofillFieldClassificationService() {
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mWrapper = new AutofillFieldClassificationServiceWrapper();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mWrapper;
+    }
+
+    /**
+     * Calculates field classification scores in a batch.
+     *
+     * <p>A field classification score is a {@code float} representing how well an
+     * {@link AutofillValue} filled matches a expected value predicted by an autofill service
+     * &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
+     *
+     * <p>The exact score depends on the algorithm used to calculate it&mdash;the service must
+     * provide at least one default algorithm (which is used when the algorithm is not specified
+     * or is invalid), but it could provide more (in which case the algorithm name should be
+     * specified by the caller when calculating the scores).
+     *
+     * <p>For example, if the service provides an algorithm named {@code EXACT_MATCH} that
+     * returns {@code 1.0} if all characters match or {@code 0.0} otherwise, a call to:
+     *
+     * <pre>
+     * service.onGetScores("EXACT_MATCH", null,
+     *   Arrays.asList(AutofillValue.forText("email1"), AutofillValue.forText("PHONE1")),
+     *   Arrays.asList("email1", "phone1"));
+     * </pre>
+     *
+     * <p>Returns:
+     *
+     * <pre>
+     * [
+     *   [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
+     *   [0.0, 0.0]  // "PHONE1" compared against ["email1", "phone1"]
+     * ];
+     * </pre>
+     *
+     * <p>If the same algorithm allows the caller to specify whether the comparisons should be
+     * case sensitive by passing a boolean option named {@code "case_sensitive"}, then a call to:
+     *
+     * <pre>
+     * Bundle algorithmOptions = new Bundle();
+     * algorithmOptions.putBoolean("case_sensitive", false);
+     *
+     * service.onGetScores("EXACT_MATCH", algorithmOptions,
+     *   Arrays.asList(AutofillValue.forText("email1"), AutofillValue.forText("PHONE1")),
+     *   Arrays.asList("email1", "phone1"));
+     * </pre>
+     *
+     * <p>Returns:
+     *
+     * <pre>
+     * [
+     *   [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
+     *   [0.0, 1.0]  // "PHONE1" compared against ["email1", "phone1"]
+     * ];
+     * </pre>
+     *
+     * @param algorithm name of the algorithm to be used to calculate the scores. If invalid or
+     * {@code null}, the default algorithm is used instead.
+     * @param algorithmOptions optional arguments to be passed to the algorithm.
+     * @param actualValues values entered by the user.
+     * @param userDataValues values predicted from the user data.
+     * @return the calculated scores of {@code actualValues} x {@code userDataValues}.
+     *
+     * {@hide}
+     *
+     * @deprecated Use {@link AutofillFieldClassificationService#onCalculateScores} instead.
+     */
+    @Nullable
+    @SystemApi
+    @Deprecated
+    public float[][] onGetScores(@Nullable String algorithm,
+            @Nullable Bundle algorithmOptions, @NonNull List<AutofillValue> actualValues,
+            @NonNull List<String> userDataValues) {
+        Log.e(TAG, "service implementation (" + getClass() + " does not implement onGetScores()");
+        return null;
+    }
+
+    /**
+     * Calculates field classification scores in a batch.
+     *
+     * <p>A field classification score is a {@code float} representing how well an
+     * {@link AutofillValue} matches a expected value predicted by an autofill service
+     * &mdash;a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}.
+     *
+     * <p>The exact score depends on the algorithm used to calculate it&mdash;the service must
+     * provide at least one default algorithm (which is used when the algorithm is not specified
+     * or is invalid), but it could provide more (in which case the algorithm name should be
+     * specified by the caller when calculating the scores).
+     *
+     * <p>For example, if the service provides an algorithm named {@code EXACT_MATCH} that
+     * returns {@code 1.0} if all characters match or {@code 0.0} otherwise, a call to:
+     *
+     * <pre>
+     * HashMap algorithms = new HashMap<>();
+     * algorithms.put("email", "EXACT_MATCH");
+     * algorithms.put("phone", "EXACT_MATCH");
+     *
+     * HashMap args = new HashMap<>();
+     * args.put("email", null);
+     * args.put("phone", null);
+     *
+     * service.onCalculateScores(Arrays.asList(AutofillValue.forText("email1"),
+     * AutofillValue.forText("PHONE1")), Arrays.asList("email1", "phone1"),
+     * Array.asList("email", "phone"), algorithms, args);
+     * </pre>
+     *
+     * <p>Returns:
+     *
+     * <pre>
+     * [
+     *   [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
+     *   [0.0, 0.0]  // "PHONE1" compared against ["email1", "phone1"]
+     * ];
+     * </pre>
+     *
+     * <p>If the same algorithm allows the caller to specify whether the comparisons should be
+     * case sensitive by passing a boolean option named {@code "case_sensitive"}, then a call to:
+     *
+     * <pre>
+     * Bundle algorithmOptions = new Bundle();
+     * algorithmOptions.putBoolean("case_sensitive", false);
+     * args.put("phone", algorithmOptions);
+     *
+     * service.onCalculateScores(Arrays.asList(AutofillValue.forText("email1"),
+     * AutofillValue.forText("PHONE1")), Arrays.asList("email1", "phone1"),
+     * Array.asList("email", "phone"), algorithms, args);
+     * </pre>
+     *
+     * <p>Returns:
+     *
+     * <pre>
+     * [
+     *   [1.0, 0.0], // "email1" compared against ["email1", "phone1"]
+     *   [0.0, 1.0]  // "PHONE1" compared against ["email1", "phone1"]
+     * ];
+     * </pre>
+     *
+     * @param actualValues values entered by the user.
+     * @param userDataValues values predicted from the user data.
+     * @param categoryIds category Ids correspoinding to userDataValues
+     * @param defaultAlgorithm default field classification algorithm
+     * @param algorithms array of field classification algorithms
+     * @return the calculated scores of {@code actualValues} x {@code userDataValues}.
+     *
+     * {@hide}
+     */
+    @Nullable
+    @SystemApi
+    public float[][] onCalculateScores(@NonNull List<AutofillValue> actualValues,
+            @NonNull List<String> userDataValues, @NonNull List<String> categoryIds,
+            @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs,
+            @Nullable Map algorithms, @Nullable Map args) {
+        Log.e(TAG, "service implementation (" + getClass()
+                + " does not implement onCalculateScore()");
+        return null;
+    }
+
+    private final class AutofillFieldClassificationServiceWrapper
+            extends IAutofillFieldClassificationService.Stub {
+        @Override
+        public void calculateScores(RemoteCallback callback, List<AutofillValue> actualValues,
+                String[] userDataValues, String[] categoryIds, String defaultAlgorithm,
+                Bundle defaultArgs, Map algorithms, Map args)
+                throws RemoteException {
+            mHandler.sendMessage(obtainMessage(
+                    AutofillFieldClassificationService::calculateScores,
+                    AutofillFieldClassificationService.this,
+                    callback, actualValues, userDataValues, categoryIds, defaultAlgorithm,
+                    defaultArgs, algorithms, args));
+        }
+    }
+
+    /**
+     * Helper class used to encapsulate a float[][] in a Parcelable.
+     *
+     * {@hide}
+     */
+    public static final class Scores implements Parcelable {
+        @NonNull
+        public final float[][] scores;
+
+        private Scores(Parcel parcel) {
+            final int size1 = parcel.readInt();
+            final int size2 = parcel.readInt();
+            scores = new float[size1][size2];
+            for (int i = 0; i < size1; i++) {
+                for (int j = 0; j < size2; j++) {
+                    scores[i][j] = parcel.readFloat();
+                }
+            }
+        }
+
+        private Scores(@NonNull float[][] scores) {
+            this.scores = scores;
+        }
+
+        @Override
+        public String toString() {
+            final int size1 = scores.length;
+            final int size2 = size1 > 0 ? scores[0].length : 0;
+            final StringBuilder builder = new StringBuilder("Scores [")
+                    .append(size1).append("x").append(size2).append("] ");
+            for (int i = 0; i < size1; i++) {
+                builder.append(i).append(": ").append(Arrays.toString(scores[i])).append(' ');
+            }
+            return builder.toString();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            int size1 = scores.length;
+            int size2 = scores[0].length;
+            parcel.writeInt(size1);
+            parcel.writeInt(size2);
+            for (int i = 0; i < size1; i++) {
+                for (int j = 0; j < size2; j++) {
+                    parcel.writeFloat(scores[i][j]);
+                }
+            }
+        }
+
+        public static final @android.annotation.NonNull Creator<Scores> CREATOR = new Creator<Scores>() {
+            @Override
+            public Scores createFromParcel(Parcel parcel) {
+                return new Scores(parcel);
+            }
+
+            @Override
+            public Scores[] newArray(int size) {
+                return new Scores[size];
+            }
+        };
+    }
+}
diff --git a/android/service/autofill/AutofillService.java b/android/service/autofill/AutofillService.java
new file mode 100644
index 0000000..188670d
--- /dev/null
+++ b/android/service/autofill/AutofillService.java
@@ -0,0 +1,714 @@
+/*
+ * 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;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Intent;
+import android.os.BaseBundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewStructure;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
+
+/**
+ * An {@code AutofillService} is a service used to automatically fill the contents of the screen
+ * on behalf of a given user - for more information about autofill, read
+ * <a href="{@docRoot}preview/features/autofill.html">Autofill Framework</a>.
+ *
+ * <p>An {@code AutofillService} is only bound to the Android System for autofill purposes if:
+ * <ol>
+ *   <li>It requires the {@code android.permission.BIND_AUTOFILL_SERVICE} permission in its
+ *       manifest.
+ *   <li>The user explicitly enables it using Android Settings (the
+ *       {@link Settings#ACTION_REQUEST_SET_AUTOFILL_SERVICE} intent can be used to launch such
+ *       Settings screen).
+ * </ol>
+ *
+ * <a name="BasicUsage"></a>
+ * <h3>Basic usage</h3>
+ *
+ * <p>The basic autofill process is defined by the workflow below:
+ * <ol>
+ *   <li>User focus an editable {@link View}.
+ *   <li>View calls {@link AutofillManager#notifyViewEntered(android.view.View)}.
+ *   <li>A {@link ViewStructure} representing all views in the screen is created.
+ *   <li>The Android System binds to the service and calls {@link #onConnected()}.
+ *   <li>The service receives the view structure through the
+ *       {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)}.
+ *   <li>The service replies through {@link FillCallback#onSuccess(FillResponse)}.
+ *   <li>The Android System calls {@link #onDisconnected()} and unbinds from the
+ *       {@code AutofillService}.
+ *   <li>The Android System displays an autofill UI with the options sent by the service.
+ *   <li>The user picks an option.
+ *   <li>The proper views are autofilled.
+ * </ol>
+ *
+ * <p>This workflow was designed to minimize the time the Android System is bound to the service;
+ * for each call, it: binds to service, waits for the reply, and unbinds right away. Furthermore,
+ * those calls are considered stateless: if the service needs to keep state between calls, it must
+ * do its own state management (keeping in mind that the service's process might be killed by the
+ * Android System when unbound; for example, if the device is running low in memory).
+ *
+ * <p>Typically, the
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} will:
+ * <ol>
+ *   <li>Parse the view structure looking for autofillable views (for example, using
+ *       {@link android.app.assist.AssistStructure.ViewNode#getAutofillHints()}.
+ *   <li>Match the autofillable views with the user's data.
+ *   <li>Create a {@link Dataset} for each set of user's data that match those fields.
+ *   <li>Fill the dataset(s) with the proper {@link AutofillId}s and {@link AutofillValue}s.
+ *   <li>Add the dataset(s) to the {@link FillResponse} passed to
+ *       {@link FillCallback#onSuccess(FillResponse)}.
+ * </ol>
+ *
+ * <p>For example, for a login screen with username and password views where the user only has one
+ * account in the service, the response could be:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ *     .addDataset(new Dataset.Builder()
+ *         .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer"))
+ *         .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer"))
+ *         .build())
+ *     .build();
+ * </pre>
+ *
+ * <p>But if the user had 2 accounts instead, the response could be:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ *     .addDataset(new Dataset.Builder()
+ *         .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer"))
+ *         .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer"))
+ *         .build())
+ *     .addDataset(new Dataset.Builder()
+ *         .setValue(id1, AutofillValue.forText("flanders"), createPresentation("flanders"))
+ *         .setValue(id2, AutofillValue.forText("OkelyDokelyDo"), createPresentation("password for flanders"))
+ *         .build())
+ *     .build();
+ * </pre>
+ *
+ * <p>If the service does not find any autofillable view in the view structure, it should pass
+ * {@code null} to {@link FillCallback#onSuccess(FillResponse)}; if the service encountered an error
+ * processing the request, it should call {@link FillCallback#onFailure(CharSequence)}. For
+ * performance reasons, it's paramount that the service calls either
+ * {@link FillCallback#onSuccess(FillResponse)} or {@link FillCallback#onFailure(CharSequence)} for
+ * each {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} received - if it
+ * doesn't, the request will eventually time out and be discarded by the Android System.
+ *
+ * <a name="SavingUserData"></a>
+ * <h3>Saving user data</h3>
+ *
+ * <p>If the service is also interested on saving the data filled by the user, it must set a
+ * {@link SaveInfo} object in the {@link FillResponse}. See {@link SaveInfo} for more details and
+ * examples.
+ *
+ * <a name="UserAuthentication"></a>
+ * <h3>User authentication</h3>
+ *
+ * <p>The service can provide an extra degree of security by requiring the user to authenticate
+ * before an app can be autofilled. The authentication is typically required in 2 scenarios:
+ * <ul>
+ *   <li>To unlock the user data (for example, using a master password or fingerprint
+ *       authentication) - see
+ * {@link FillResponse.Builder#setAuthentication(AutofillId[], android.content.IntentSender, android.widget.RemoteViews)}.
+ *   <li>To unlock a specific dataset (for example, by providing a CVC for a credit card) - see
+ *       {@link Dataset.Builder#setAuthentication(android.content.IntentSender)}.
+ * </ul>
+ *
+ * <p>When using authentication, it is recommended to encrypt only the sensitive data and leave
+ * labels unencrypted, so they can be used on presentation views. For example, if the user has a
+ * home and a work address, the {@code Home} and {@code Work} labels should be stored unencrypted
+ * (since they don't have any sensitive data) while the address data per se could be stored in an
+ * encrypted storage. Then when the user chooses the {@code Home} dataset, the platform starts
+ * the authentication flow, and the service can decrypt the sensitive data.
+ *
+ * <p>The authentication mechanism can also be used in scenarios where the service needs multiple
+ * steps to determine the datasets that can fill a screen. For example, when autofilling a financial
+ * app where the user has accounts for multiple banks, the workflow could be:
+ *
+ * <ol>
+ *   <li>The first {@link FillResponse} contains datasets with the credentials for the financial
+ *       app, plus a "fake" dataset whose presentation says "Tap here for banking apps credentials".
+ *   <li>When the user selects the fake dataset, the service displays a dialog with available
+ *       banking apps.
+ *   <li>When the user select a banking app, the service replies with a new {@link FillResponse}
+ *       containing the datasets for that bank.
+ * </ol>
+ *
+ * <p>Another example of multiple-steps dataset selection is when the service stores the user
+ * credentials in "vaults": the first response would contain fake datasets with the vault names,
+ * and the subsequent response would contain the app credentials stored in that vault.
+ *
+ * <a name="DataPartioning"></a>
+ * <h3>Data partitioning</h3>
+ *
+ * <p>The autofillable views in a screen should be grouped in logical groups called "partitions".
+ * Typical partitions are:
+ * <ul>
+ *   <li>Credentials (username/email address, password).
+ *   <li>Address (street, city, state, zip code, etc).
+ *   <li>Payment info (credit card number, expiration date, and verification code).
+ * </ul>
+ * <p>For security reasons, when a screen has more than one partition, it's paramount that the
+ * contents of a dataset do not spawn multiple partitions, specially when one of the partitions
+ * contains data that is not specific to the application being autofilled. For example, a dataset
+ * should not contain fields for username, password, and credit card information. The reason for
+ * this rule is that a malicious app could draft a view structure where the credit card fields
+ * are not visible, so when the user selects a dataset from the username UI, the credit card info is
+ * released to the application without the user knowledge. Similarly, it's recommended to always
+ * protect a dataset that contains sensitive information by requiring dataset authentication
+ * (see {@link Dataset.Builder#setAuthentication(android.content.IntentSender)}), and to include
+ * info about the "primary" field of the partition in the custom presentation for "secondary"
+ * fields&mdash;that would prevent a malicious app from getting the "primary" fields without the
+ * user realizing they're being released (for example, a malicious app could have fields for a
+ * credit card number, verification code, and expiration date crafted in a way that just the latter
+ * is visible; by explicitly indicating the expiration date is related to a given credit card
+ * number, the service would be providing a visual clue for the users to check what would be
+ * released upon selecting that field).
+ *
+ * <p>When the service detects that a screen has multiple partitions, it should return a
+ * {@link FillResponse} with just the datasets for the partition that originated the request (i.e.,
+ * the partition that has the {@link android.app.assist.AssistStructure.ViewNode} whose
+ * {@link android.app.assist.AssistStructure.ViewNode#isFocused()} returns {@code true}); then if
+ * the user selects a field from a different partition, the Android System will make another
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} call for that partition,
+ * and so on.
+ *
+ * <p>Notice that when the user autofill a partition with the data provided by the service and the
+ * user did not change these fields, the autofilled value is sent back to the service in the
+ * subsequent calls (and can be obtained by calling
+ * {@link android.app.assist.AssistStructure.ViewNode#getAutofillValue()}). This is useful in the
+ * cases where the service must create datasets for a partition based on the choice made in a
+ * previous partition. For example, the 1st response for a screen that have credentials and address
+ * partitions could be:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ *     .addDataset(new Dataset.Builder() // partition 1 (credentials)
+ *         .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer"))
+ *         .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer"))
+ *         .build())
+ *     .addDataset(new Dataset.Builder() // partition 1 (credentials)
+ *         .setValue(id1, AutofillValue.forText("flanders"), createPresentation("flanders"))
+ *         .setValue(id2, AutofillValue.forText("OkelyDokelyDo"), createPresentation("password for flanders"))
+ *         .build())
+ *     .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ *         new AutofillId[] { id1, id2 })
+ *             .build())
+ *     .build();
+ * </pre>
+ *
+ * <p>Then if the user selected {@code flanders}, the service would get a new
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} call, with the values of
+ * the fields {@code id1} and {@code id2} prepopulated, so the service could then fetch the address
+ * for the Flanders account and return the following {@link FillResponse} for the address partition:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ *     .addDataset(new Dataset.Builder() // partition 2 (address)
+ *         .setValue(id3, AutofillValue.forText("744 Evergreen Terrace"), createPresentation("744 Evergreen Terrace")) // street
+ *         .setValue(id4, AutofillValue.forText("Springfield"), createPresentation("Springfield")) // city
+ *         .build())
+ *     .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_PASSWORD | SaveInfo.SAVE_DATA_TYPE_ADDRESS,
+ *         new AutofillId[] { id1, id2 }) // username and password
+ *              .setOptionalIds(new AutofillId[] { id3, id4 }) // state and zipcode
+ *             .build())
+ *     .build();
+ * </pre>
+ *
+ * <p>When the service returns multiple {@link FillResponse}, the last one overrides the previous;
+ * that's why the {@link SaveInfo} in the 2nd request above has the info for both partitions.
+ *
+ * <a name="PackageVerification"></a>
+ * <h3>Package verification</h3>
+ *
+ * <p>When autofilling app-specific data (like username and password), the service must verify
+ * the authenticity of the request by obtaining all signing certificates of the app being
+ * autofilled, and only fulfilling the request when they match the values that were
+ * obtained when the data was first saved &mdash; such verification is necessary to avoid phishing
+ * attempts by apps that were sideloaded in the device with the same package name of another app.
+ * Here's an example on how to achieve that by hashing the signing certificates:
+ *
+ * <pre class="prettyprint">
+ * private String getCertificatesHash(String packageName) throws Exception {
+ *   PackageManager pm = mContext.getPackageManager();
+ *   PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ *   ArrayList<String> hashes = new ArrayList<>(info.signatures.length);
+ *   for (Signature sig : info.signatures) {
+ *     byte[] cert = sig.toByteArray();
+ *     MessageDigest md = MessageDigest.getInstance("SHA-256");
+ *     md.update(cert);
+ *     hashes.add(toHexString(md.digest()));
+ *   }
+ *   Collections.sort(hashes);
+ *   StringBuilder hash = new StringBuilder();
+ *   for (int i = 0; i < hashes.size(); i++) {
+ *     hash.append(hashes.get(i));
+ *   }
+ *   return hash.toString();
+ * }
+ * </pre>
+ *
+ * <p>If the service did not store the signing certificates data the first time the data was saved
+ * &mdash; for example, because the data was created by a previous version of the app that did not
+ * use the Autofill Framework &mdash; the service should warn the user that the authenticity of the
+ * app cannot be confirmed (see an example on how to show such warning in the
+ * <a href="#WebSecurityDisclaimer">Web security</a> section below), and if the user agrees,
+ * then the service could save the data from the signing ceriticates for future use.
+ *
+ * <a name="IgnoringViews"></a>
+ * <h3>Ignoring views</h3>
+ *
+ * <p>If the service find views that cannot be autofilled (for example, a text field representing
+ * the response to a Captcha challenge), it should mark those views as ignored by
+ * calling {@link FillResponse.Builder#setIgnoredIds(AutofillId...)} so the system does not trigger
+ * a new {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} when these views are
+ * focused.
+ *
+ * <a name="WebSecurity"></a>
+ * <h3>Web security</h3>
+ *
+ * <p>When handling autofill requests that represent web pages (typically
+ * view structures whose root's {@link android.app.assist.AssistStructure.ViewNode#getClassName()}
+ * is a {@link android.webkit.WebView}), the service should take the following steps to verify if
+ * the structure can be autofilled with the data associated with the app requesting it:
+ *
+ * <ol>
+ *   <li>Use the {@link android.app.assist.AssistStructure.ViewNode#getWebDomain()} to get the
+ *       source of the document.
+ *   <li>Get the canonical domain using the
+ *       <a href="https://publicsuffix.org/">Public Suffix List</a> (see example below).
+ *   <li>Use <a href="https://developers.google.com/digital-asset-links/">Digital Asset Links</a>
+ *       to obtain the package name and certificate fingerprint of the package corresponding to
+ *       the canonical domain.
+ *   <li>Make sure the certificate fingerprint matches the value returned by Package Manager
+ *       (see "Package verification" section above).
+ * </ol>
+ *
+ * <p>Here's an example on how to get the canonical domain using
+ * <a href="https://github.com/google/guava">Guava</a>:
+ *
+ * <pre class="prettyprint">
+ * private static String getCanonicalDomain(String domain) {
+ *   InternetDomainName idn = InternetDomainName.from(domain);
+ *   while (idn != null && !idn.isTopPrivateDomain()) {
+ *     idn = idn.parent();
+ *   }
+ *   return idn == null ? null : idn.toString();
+ * }
+ * </pre>
+ *
+ * <a name="WebSecurityDisclaimer"></a>
+ * <p>If the association between the web domain and app package cannot be verified through the steps
+ * above, but the service thinks that it is appropriate to fill persisted credentials that are
+ * stored for the web domain, the service should warn the user about the potential data
+ * leakage first, and ask for the user to confirm. For example, the service could:
+ *
+ * <ol>
+ *   <li>Create a dataset that requires
+ *       {@link Dataset.Builder#setAuthentication(android.content.IntentSender) authentication} to
+ *       unlock.
+ *   <li>Include the web domain in the custom presentation for the
+ *       {@link Dataset.Builder#setValue(AutofillId, AutofillValue, android.widget.RemoteViews)
+ *       dataset value}.
+ *   <li>When the user selects that dataset, show a disclaimer dialog explaining that the app is
+ *       requesting credentials for a web domain, but the service could not verify if the app owns
+ *       that domain. If the user agrees, then the service can unlock the dataset.
+ *   <li>Similarly, when adding a {@link SaveInfo} object for the request, the service should
+ *       include the above disclaimer in the {@link SaveInfo.Builder#setDescription(CharSequence)}.
+ * </ol>
+ *
+ * <p>This same procedure could also be used when the autofillable data is contained inside an
+ * {@code IFRAME}, in which case the WebView generates a new autofill context when a node inside
+ * the {@code IFRAME} is focused, with the root node containing the {@code IFRAME}'s {@code src}
+ * attribute on {@link android.app.assist.AssistStructure.ViewNode#getWebDomain()}. A typical and
+ * legitimate use case for this scenario is a financial app that allows the user
+ * to login on different bank accounts. For example, a financial app {@code my_financial_app} could
+ * use a WebView that loads contents from {@code banklogin.my_financial_app.com}, which contains an
+ * {@code IFRAME} node whose {@code src} attribute is {@code login.some_bank.com}. When fulfilling
+ * that request, the service could add an
+ * {@link Dataset.Builder#setAuthentication(android.content.IntentSender) authenticated dataset}
+ * whose presentation displays "Username for some_bank.com" and
+ * "Password for some_bank.com". Then when the user taps one of these options, the service
+ * shows the disclaimer dialog explaining that selecting that option would release the
+ * {@code login.some_bank.com} credentials to the {@code my_financial_app}; if the user agrees,
+ * then the service returns an unlocked dataset with the {@code some_bank.com} credentials.
+ *
+ * <p><b>Note:</b> The autofill service could also whitelist well-known browser apps and skip the
+ * verifications above, as long as the service can verify the authenticity of the browser app by
+ * checking its signing certificate.
+ *
+ * <a name="MultipleStepsSave"></a>
+ * <h3>Saving when data is split in multiple screens</h3>
+ *
+ * Apps often split the user data in multiple screens in the same activity, specially in
+ * activities used to create a new user account. For example, the first screen asks for a username,
+ * and if the username is available, it moves to a second screen, which asks for a password.
+ *
+ * <p>It's tricky to handle save for autofill in these situations, because the autofill service must
+ * wait until the user enters both fields before the autofill save UI can be shown. But it can be
+ * done by following the steps below:
+ *
+ * <ol>
+ * <li>In the first
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service
+ * adds a {@link FillResponse.Builder#setClientState(android.os.Bundle) client state bundle} in
+ * the response, containing the autofill ids of the partial fields present in the screen.
+ * <li>In the second
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service
+ * retrieves the {@link FillRequest#getClientState() client state bundle}, gets the autofill ids
+ * set in the previous request from the client state, and adds these ids and the
+ * {@link SaveInfo#FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} to the {@link SaveInfo} used in the second
+ * response.
+ * <li>In the {@link #onSaveRequest(SaveRequest, SaveCallback) save request}, the service uses the
+ * proper {@link FillContext fill contexts} to get the value of each field (there is one fill
+ * context per fill request).
+ * </ol>
+ *
+ * <p>For example, in an app that uses 2 steps for the username and password fields, the workflow
+ * would be:
+ * <pre class="prettyprint">
+ *  // On first fill request
+ *  AutofillId usernameId = // parse from AssistStructure;
+ *  Bundle clientState = new Bundle();
+ *  clientState.putParcelable("usernameId", usernameId);
+ *  fillCallback.onSuccess(
+ *    new FillResponse.Builder()
+ *        .setClientState(clientState)
+ *        .setSaveInfo(new SaveInfo
+ *             .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME, new AutofillId[] {usernameId})
+ *             .build())
+ *        .build());
+ *
+ *  // On second fill request
+ *  Bundle clientState = fillRequest.getClientState();
+ *  AutofillId usernameId = clientState.getParcelable("usernameId");
+ *  AutofillId passwordId = // parse from AssistStructure
+ *  clientState.putParcelable("passwordId", passwordId);
+ *  fillCallback.onSuccess(
+ *    new FillResponse.Builder()
+ *        .setClientState(clientState)
+ *        .setSaveInfo(new SaveInfo
+ *             .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ *                      new AutofillId[] {usernameId, passwordId})
+ *             .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
+ *             .build())
+ *        .build());
+ *
+ *  // On save request
+ *  Bundle clientState = saveRequest.getClientState();
+ *  AutofillId usernameId = clientState.getParcelable("usernameId");
+ *  AutofillId passwordId = clientState.getParcelable("passwordId");
+ *  List<FillContext> fillContexts = saveRequest.getFillContexts();
+ *
+ *  FillContext usernameContext = fillContexts.get(0);
+ *  ViewNode usernameNode = findNodeByAutofillId(usernameContext.getStructure(), usernameId);
+ *  AutofillValue username = usernameNode.getAutofillValue().getTextValue().toString();
+ *
+ *  FillContext passwordContext = fillContexts.get(1);
+ *  ViewNode passwordNode = findNodeByAutofillId(passwordContext.getStructure(), passwordId);
+ *  AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
+ *
+ *  save(username, password);
+ *  </pre>
+ *
+ * <a name="Privacy"></a>
+ * <h3>Privacy</h3>
+ *
+ * <p>The {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} method is called
+ * without the user content. The Android system strips some properties of the
+ * {@link android.app.assist.AssistStructure.ViewNode view nodes} passed to this call, but not all
+ * of them. For example, the data provided in the {@link android.view.ViewStructure.HtmlInfo}
+ * objects set by {@link android.webkit.WebView} is never stripped out.
+ *
+ * <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="FieldClassification"></a>
+ * <h3>Metrics and field classification</h3
+ *
+ * <p>The service can call {@link #getFillEventHistory()} to get metrics representing the user
+ * actions, and then use these metrics to improve its heuristics.
+ *
+ * <p>Prior to Android {@link android.os.Build.VERSION_CODES#P}, the metrics covered just the
+ * scenarios where the service knew how to autofill an activity, but Android
+ * {@link android.os.Build.VERSION_CODES#P} introduced a new mechanism called field classification,
+ * which allows the service to dinamically classify the meaning of fields based on the existing user
+ * data known by the service.
+ *
+ * <p>Typically, field classification can be used to detect fields that can be autofilled with
+ * user data that is not associated with a specific app&mdash;such as email and physical
+ * address. Once the service identifies that a such field was manually filled by the user, the
+ * service could use this signal to improve its heuristics on subsequent requests (for example, by
+ * infering which resource ids are associated with known fields).
+ *
+ * <p>The field classification workflow involves 4 steps:
+ *
+ * <ol>
+ *   <li>Set the user data through {@link AutofillManager#setUserData(UserData)}. This data is
+ *   cached until the system restarts (or the service is disabled), so it doesn't need to be set for
+ *   all requests.
+ *   <li>Identify which fields should be analysed by calling
+ *   {@link FillResponse.Builder#setFieldClassificationIds(AutofillId...)}.
+ *   <li>Verify the results through {@link FillEventHistory.Event#getFieldsClassification()}.
+ *   <li>Use the results to dynamically create {@link Dataset} or {@link SaveInfo} objects in
+ *   subsequent requests.
+ * </ol>
+ *
+ * <p>The field classification is an expensive operation and should be used carefully, otherwise it
+ * can reach its rate limit and get blocked by the Android System. Ideally, it should be used just
+ * in cases where the service could not determine how an activity can be autofilled, but it has a
+ * strong suspicious that it could. For example, if an activity has four or more fields and one of
+ * them is a list, chances are that these are address fields (like address, city, state, and
+ * zip code).
+ *
+ * <a name="CompatibilityMode"></a>
+ * <h3>Compatibility mode</h3>
+ *
+ * <p>Apps that use standard Android widgets support autofill out-of-the-box and need to do
+ * very little to improve their user experience (annotating autofillable views and providing
+ * autofill hints). However, some apps (typically browsers) do their own rendering and the rendered
+ * content may contain semantic structure that needs to be surfaced to the autofill framework. The
+ * platform exposes APIs to achieve this, however it could take some time until these apps implement
+ * autofill support.
+ *
+ * <p>To enable autofill for such apps the platform provides a compatibility mode in which the
+ * platform would fall back to the accessibility APIs to generate the state reported to autofill
+ * services and fill data. This mode needs to be explicitly requested for a given package up
+ * to a specified max version code allowing clean migration path when the target app begins to
+ * support autofill natively. Note that enabling compatibility may degrade performance for the
+ * target package and should be used with caution. The platform supports whitelisting which packages
+ * can be targeted in compatibility mode to ensure this mode is used only when needed and as long
+ * as needed.
+ *
+ * <p>You can request compatibility mode for packages of interest in the meta-data resource
+ * associated with your service. Below is a sample service declaration:
+ *
+ * <pre> &lt;service android:name=".MyAutofillService"
+ *              android:permission="android.permission.BIND_AUTOFILL_SERVICE"&gt;
+ *     &lt;intent-filter&gt;
+ *         &lt;action android:name="android.service.autofill.AutofillService" /&gt;
+ *     &lt;/intent-filter&gt;
+ *     &lt;meta-data android:name="android.autofill" android:resource="@xml/autofillservice" /&gt;
+ * &lt;/service&gt;</pre>
+ *
+ * <p>In the XML file you can specify one or more packages for which to enable compatibility
+ * mode. Below is a sample meta-data declaration:
+ *
+ * <pre> &lt;autofill-service xmlns:android="http://schemas.android.com/apk/res/android"&gt;
+ *     &lt;compatibility-package android:name="foo.bar.baz" android:maxLongVersionCode="1000000000"/&gt;
+ * &lt;/autofill-service&gt;</pre>
+ *
+ * <p>Notice that compatibility mode has limitations such as:
+ * <ul>
+ * <li>No manual autofill requests. Hence, the {@link FillRequest}
+ * {@link FillRequest#getFlags() flags} never have the {@link FillRequest#FLAG_MANUAL_REQUEST} flag.
+ * <li>The value of password fields are most likely masked&mdash;for example, {@code ****} instead
+ * of {@code 1234}. Hence, you must be careful when using these values to avoid updating the user
+ * data with invalid input. For example, when you parse the {@link FillRequest} and detect a
+ * password field, you could check if its
+ * {@link android.app.assist.AssistStructure.ViewNode#getInputType()
+ * input type} has password flags and if so, don't add it to the {@link SaveInfo} object.
+ * <li>The autofill context is not always {@link AutofillManager#commit() committed} when an HTML
+ * form is submitted. Hence, you must use other mechanisms to trigger save, such as setting the
+ * {@link SaveInfo#FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag on {@link SaveInfo.Builder#setFlags(int)}
+ * or using {@link SaveInfo.Builder#setTriggerId(AutofillId)}.
+ * <li>Browsers often provide their own autofill management system. When both the browser and
+ * the platform render an autofill dialog at the same time, the result can be confusing to the user.
+ * Such browsers typically offer an option for users to disable autofill, so your service should
+ * also allow users to disable compatiblity mode for specific apps. That way, it is up to the user
+ * to decide which autofill mechanism&mdash;the browser's or the platform's&mdash;should be used.
+ * </ul>
+ */
+public abstract class AutofillService extends Service {
+    private static final String TAG = "AutofillService";
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     * To be supported, the service must also require the
+     * {@link android.Manifest.permission#BIND_AUTOFILL_SERVICE} permission so
+     * that other applications can not abuse it.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE = "android.service.autofill.AutofillService";
+
+    /**
+     * Name under which a AutoFillService component publishes information about itself.
+     * This meta-data should reference an XML resource containing a
+     * <code>&lt;{@link
+     * android.R.styleable#AutofillService autofill-service}&gt;</code> tag.
+     * This is a a sample XML file configuring an AutoFillService:
+     * <pre> &lt;autofill-service
+     *     android:settingsActivity="foo.bar.SettingsActivity"
+     *     . . .
+     * /&gt;</pre>
+     */
+    public static final String SERVICE_META_DATA = "android.autofill";
+
+    private final IAutoFillService mInterface = new IAutoFillService.Stub() {
+        @Override
+        public void onConnectedStateChanged(boolean connected) {
+            mHandler.sendMessage(obtainMessage(
+                    connected ? AutofillService::onConnected : AutofillService::onDisconnected,
+                    AutofillService.this));
+        }
+
+        @Override
+        public void onFillRequest(FillRequest request, IFillCallback callback) {
+            ICancellationSignal transport = CancellationSignal.createTransport();
+            try {
+                callback.onCancellable(transport);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            mHandler.sendMessage(obtainMessage(
+                    AutofillService::onFillRequest,
+                    AutofillService.this, request, CancellationSignal.fromTransport(transport),
+                    new FillCallback(callback, request.getId())));
+        }
+
+        @Override
+        public void onSaveRequest(SaveRequest request, ISaveCallback callback) {
+            mHandler.sendMessage(obtainMessage(
+                    AutofillService::onSaveRequest,
+                    AutofillService.this, request, new SaveCallback(callback)));
+        }
+    };
+
+    private Handler mHandler;
+
+    @CallSuper
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mHandler = new Handler(Looper.getMainLooper(), null, true);
+        BaseBundle.setShouldDefuse(true);
+    }
+
+    @Override
+    public final IBinder onBind(Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mInterface.asBinder();
+        }
+        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+        return null;
+    }
+
+    /**
+     * Called when the Android system connects to service.
+     *
+     * <p>You should generally do initialization here rather than in {@link #onCreate}.
+     */
+    public void onConnected() {
+    }
+
+    /**
+     * Called by the Android system do decide if a screen can be autofilled by the service.
+     *
+     * <p>Service must call one of the {@link FillCallback} methods (like
+     * {@link FillCallback#onSuccess(FillResponse)}
+     * or {@link FillCallback#onFailure(CharSequence)})
+     * to notify the result of the request.
+     *
+     * @param request the {@link FillRequest request} to handle.
+     *        See {@link FillResponse} for examples of multiple-sections requests.
+     * @param cancellationSignal signal for observing cancellation requests. The system will use
+     *     this to notify you that the fill result is no longer needed and you should stop
+     *     handling this fill request in order to save resources.
+     * @param callback object used to notify the result of the request.
+     */
+    public abstract void onFillRequest(@NonNull FillRequest request,
+            @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
+
+    /**
+     * Called when the user requests the service to save the contents of a screen.
+     *
+     * <p>If the service could not handle the request right away&mdash;for example, because it must
+     * launch an activity asking the user to authenticate first or because the network is
+     * down&mdash;the service could keep the {@link SaveRequest request} and reuse it later,
+     * but the service <b>must always</b> call {@link SaveCallback#onSuccess()} or
+     * {@link SaveCallback#onSuccess(android.content.IntentSender)} right away.
+     *
+     * <p><b>Note:</b> To retrieve the actual value of fields input by the user, the service
+     * should call
+     * {@link android.app.assist.AssistStructure.ViewNode#getAutofillValue()}; if it calls
+     * {@link android.app.assist.AssistStructure.ViewNode#getText()} or other methods, there is no
+     * guarantee such method will return the most recent value of the field.
+     *
+     * @param request the {@link SaveRequest request} to handle.
+     *        See {@link FillResponse} for examples of multiple-sections requests.
+     * @param callback object used to notify the result of the request.
+     */
+    public abstract void onSaveRequest(@NonNull SaveRequest request,
+            @NonNull SaveCallback callback);
+
+    /**
+     * Called when the Android system disconnects from the service.
+     *
+     * <p> At this point this service may no longer be an active {@link AutofillService}.
+     * It should not make calls on {@link AutofillManager} that requires the caller to be
+     * the current service.
+     */
+    public void onDisconnected() {
+    }
+
+    /**
+     * Gets the events that happened after the last
+     * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
+     * call.
+     *
+     * <p>This method is typically used to keep track of previous user actions to optimize further
+     * requests. For example, the service might return email addresses in alphabetical order by
+     * default, but change that order based on the address the user picked on previous requests.
+     *
+     * <p>The history is not persisted over reboots, and it's cleared every time the service
+     * replies to a {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} by calling
+     * {@link FillCallback#onSuccess(FillResponse)} or {@link FillCallback#onFailure(CharSequence)}
+     * (if the service doesn't call any of these methods, the history will clear out after some
+     * pre-defined time). Hence, the service should call {@link #getFillEventHistory()} before
+     * finishing the {@link FillCallback}.
+     *
+     * @return The history or {@code null} if there are no events.
+     *
+     * @throws RuntimeException if the event history could not be retrieved.
+     */
+    @Nullable public final FillEventHistory getFillEventHistory() {
+        final AutofillManager afm = getSystemService(AutofillManager.class);
+
+        if (afm == null) {
+            return null;
+        } else {
+            return afm.getFillEventHistory();
+        }
+    }
+}
diff --git a/android/service/autofill/AutofillServiceHelper.java b/android/service/autofill/AutofillServiceHelper.java
new file mode 100644
index 0000000..13fedba
--- /dev/null
+++ b/android/service/autofill/AutofillServiceHelper.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.annotation.Nullable;
+import android.view.autofill.AutofillId;
+
+import com.android.internal.util.Preconditions;
+
+/** @hide */
+final class AutofillServiceHelper {
+
+    static AutofillId[] assertValid(@Nullable AutofillId[] ids) {
+        Preconditions.checkArgument(ids != null && ids.length > 0, "must have at least one id");
+        // Can't use Preconditions.checkArrayElementsNotNull() because it throws NPE instead of IAE
+        for (int i = 0; i < ids.length; ++i) {
+            if (ids[i] == null) {
+                throw new IllegalArgumentException("ids[" + i + "] must not be null");
+            }
+        }
+        return ids;
+    }
+
+    private AutofillServiceHelper() {
+        throw new UnsupportedOperationException("contains static members only");
+    }
+}
diff --git a/android/service/autofill/AutofillServiceInfo.java b/android/service/autofill/AutofillServiceInfo.java
new file mode 100644
index 0000000..fbc25a6
--- /dev/null
+++ b/android/service/autofill/AutofillServiceInfo.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2016 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.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.metrics.LogMaker;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.R;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * {@link ServiceInfo} and meta-data about an {@link AutofillService}.
+ *
+ * @hide
+ */
+public final class AutofillServiceInfo {
+    private static final String TAG = "AutofillServiceInfo";
+
+    private static final String TAG_AUTOFILL_SERVICE = "autofill-service";
+    private static final String TAG_COMPATIBILITY_PACKAGE = "compatibility-package";
+
+    private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, int userHandle)
+            throws PackageManager.NameNotFoundException {
+        try {
+            ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(
+                    comp,
+                    PackageManager.GET_META_DATA,
+                    userHandle);
+            if (si != null) {
+                return si;
+            }
+        } catch (RemoteException e) {
+        }
+        throw new PackageManager.NameNotFoundException(comp.toString());
+    }
+
+    @NonNull
+    private final ServiceInfo mServiceInfo;
+
+    @Nullable
+    private final String mSettingsActivity;
+
+    @Nullable
+    private final ArrayMap<String, Long> mCompatibilityPackages;
+
+    private final boolean mInlineSuggestionsEnabled;
+
+    public AutofillServiceInfo(Context context, ComponentName comp, int userHandle)
+            throws PackageManager.NameNotFoundException {
+        this(context, getServiceInfoOrThrow(comp, userHandle));
+    }
+
+    public AutofillServiceInfo(Context context, ServiceInfo si) {
+        // Check for permissions.
+        if (!Manifest.permission.BIND_AUTOFILL_SERVICE.equals(si.permission)) {
+            if (Manifest.permission.BIND_AUTOFILL.equals(si.permission)) {
+                // Let it go for now...
+                Log.w(TAG, "AutofillService from '" + si.packageName + "' uses unsupported "
+                        + "permission " + Manifest.permission.BIND_AUTOFILL + ". It works for "
+                        + "now, but might not be supported on future releases");
+                new MetricsLogger().write(new LogMaker(MetricsEvent.AUTOFILL_INVALID_PERMISSION)
+                        .setPackageName(si.packageName));
+            } else {
+                Log.w(TAG, "AutofillService from '" + si.packageName
+                        + "' does not require permission "
+                        + Manifest.permission.BIND_AUTOFILL_SERVICE);
+                throw new SecurityException("Service does not require permission "
+                        + Manifest.permission.BIND_AUTOFILL_SERVICE);
+            }
+        }
+
+        mServiceInfo = si;
+
+        // Get the AutoFill metadata, if declared.
+        final XmlResourceParser parser = si.loadXmlMetaData(context.getPackageManager(),
+                AutofillService.SERVICE_META_DATA);
+        if (parser == null) {
+            mSettingsActivity = null;
+            mCompatibilityPackages = null;
+            mInlineSuggestionsEnabled = false;
+            return;
+        }
+
+        String settingsActivity = null;
+        ArrayMap<String, Long> compatibilityPackages = null;
+        boolean inlineSuggestionsEnabled = false; // false by default.
+
+        try {
+            final Resources resources = context.getPackageManager().getResourcesForApplication(
+                    si.applicationInfo);
+
+            int type = 0;
+            while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+                type = parser.next();
+            }
+
+            if (TAG_AUTOFILL_SERVICE.equals(parser.getName())) {
+                final AttributeSet allAttributes = Xml.asAttributeSet(parser);
+                TypedArray afsAttributes = null;
+                try {
+                    afsAttributes = resources.obtainAttributes(allAttributes,
+                            com.android.internal.R.styleable.AutofillService);
+                    settingsActivity = afsAttributes.getString(
+                            R.styleable.AutofillService_settingsActivity);
+                    inlineSuggestionsEnabled = afsAttributes.getBoolean(
+                            R.styleable.AutofillService_supportsInlineSuggestions, false);
+                } finally {
+                    if (afsAttributes != null) {
+                        afsAttributes.recycle();
+                    }
+                }
+                compatibilityPackages = parseCompatibilityPackages(parser, resources);
+            } else {
+                Log.e(TAG, "Meta-data does not start with autofill-service tag");
+            }
+        } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
+            Log.e(TAG, "Error parsing auto fill service meta-data", e);
+        }
+
+        mSettingsActivity = settingsActivity;
+        mCompatibilityPackages = compatibilityPackages;
+        mInlineSuggestionsEnabled = inlineSuggestionsEnabled;
+    }
+
+    private ArrayMap<String, Long> parseCompatibilityPackages(XmlPullParser parser,
+            Resources resources) throws IOException, XmlPullParserException {
+        ArrayMap<String, Long> compatibilityPackages = null;
+
+        final int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            if (TAG_COMPATIBILITY_PACKAGE.equals(parser.getName())) {
+                TypedArray cpAttributes = null;
+                try {
+                    final AttributeSet allAttributes = Xml.asAttributeSet(parser);
+
+                    cpAttributes = resources.obtainAttributes(allAttributes,
+                           R.styleable.AutofillService_CompatibilityPackage);
+
+                    final String name = cpAttributes.getString(
+                            R.styleable.AutofillService_CompatibilityPackage_name);
+                    if (TextUtils.isEmpty(name)) {
+                        Log.e(TAG, "Invalid compatibility package:" + name);
+                        break;
+                    }
+
+                    final String maxVersionCodeStr = cpAttributes.getString(
+                            R.styleable.AutofillService_CompatibilityPackage_maxLongVersionCode);
+                    final Long maxVersionCode;
+                    if (maxVersionCodeStr != null) {
+                        try {
+                            maxVersionCode = Long.parseLong(maxVersionCodeStr);
+                        } catch (NumberFormatException e) {
+                            Log.e(TAG, "Invalid compatibility max version code:"
+                                    + maxVersionCodeStr);
+                            break;
+                        }
+                        if (maxVersionCode < 0) {
+                            Log.e(TAG, "Invalid compatibility max version code:"
+                                    + maxVersionCode);
+                            break;
+                        }
+                    } else {
+                        maxVersionCode = Long.MAX_VALUE;
+                    }
+                    if (compatibilityPackages == null) {
+                        compatibilityPackages = new ArrayMap<>();
+                    }
+                    compatibilityPackages.put(name, maxVersionCode);
+                } finally {
+                    XmlUtils.skipCurrentTag(parser);
+                    if (cpAttributes != null) {
+                        cpAttributes.recycle();
+                    }
+                }
+            }
+        }
+
+        return compatibilityPackages;
+    }
+
+    public ServiceInfo getServiceInfo() {
+        return mServiceInfo;
+    }
+
+    @Nullable
+    public String getSettingsActivity() {
+        return mSettingsActivity;
+    }
+
+    public ArrayMap<String, Long> getCompatibilityPackages() {
+        return mCompatibilityPackages;
+    }
+
+    public boolean isInlineSuggestionsEnabled() {
+        return mInlineSuggestionsEnabled;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append(getClass().getSimpleName());
+        builder.append("[").append(mServiceInfo);
+        builder.append(", settings:").append(mSettingsActivity);
+        builder.append(", hasCompatPckgs:").append(mCompatibilityPackages != null
+                && !mCompatibilityPackages.isEmpty()).append("]");
+        builder.append(", inline suggestions enabled:").append(mInlineSuggestionsEnabled);
+        return builder.toString();
+    }
+
+    /**
+     * Dumps it!
+     */
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("Component: "); pw.println(getServiceInfo().getComponentName());
+        pw.print(prefix); pw.print("Settings: "); pw.println(mSettingsActivity);
+        pw.print(prefix); pw.print("Compat packages: "); pw.println(mCompatibilityPackages);
+        pw.print(prefix); pw.print("Inline Suggestions Enabled: ");
+        pw.println(mInlineSuggestionsEnabled);
+    }
+}
diff --git a/android/service/autofill/BatchUpdates.java b/android/service/autofill/BatchUpdates.java
new file mode 100644
index 0000000..e0b1c2f
--- /dev/null
+++ b/android/service/autofill/BatchUpdates.java
@@ -0,0 +1,219 @@
+/*
+ * 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;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+
+/**
+ * Defines actions to be applied to a {@link RemoteViews template presentation}.
+ *
+ *
+ * <p>It supports 2 types of actions:
+ *
+ * <ol>
+ *   <li>{@link RemoteViews Actions} to be applied to the template.
+ *   <li>{@link Transformation Transformations} to be applied on child views.
+ * </ol>
+ *
+ * <p>Typically used on {@link CustomDescription custom descriptions} to conditionally display
+ * differents views based on user input - see
+ * {@link CustomDescription.Builder#batchUpdate(Validator, BatchUpdates)} for more information.
+ */
+public final class BatchUpdates implements Parcelable {
+
+    private final ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+    private final RemoteViews mUpdates;
+
+    private BatchUpdates(Builder builder) {
+        mTransformations = builder.mTransformations;
+        mUpdates = builder.mUpdates;
+    }
+
+    /** @hide */
+    @Nullable
+    public ArrayList<Pair<Integer, InternalTransformation>> getTransformations() {
+        return mTransformations;
+    }
+
+    /** @hide */
+    @Nullable
+    public RemoteViews getUpdates() {
+        return mUpdates;
+    }
+
+    /**
+     * Builder for {@link BatchUpdates} objects.
+     */
+    public static class Builder {
+        private RemoteViews mUpdates;
+
+        private boolean mDestroyed;
+        private ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+
+        /**
+         * Applies the {@code updates} in the underlying presentation template.
+         *
+         * <p><b>Note:</b> The updates are applied before the
+         * {@link #transformChild(int, Transformation) transformations} are applied to the children
+         * views.
+         *
+         * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+         * or background color: Autofill on different platforms may have different themes.
+         *
+         * @param updates a {@link RemoteViews} with the updated actions to be applied in the
+         * underlying presentation template.
+         *
+         * @return this builder
+         * @throws IllegalArgumentException if {@code condition} is not a class provided
+         * by the Android System.
+         */
+        public Builder updateTemplate(@NonNull RemoteViews updates) {
+            throwIfDestroyed();
+            mUpdates = Preconditions.checkNotNull(updates);
+            return this;
+        }
+
+        /**
+         * Adds a transformation to replace the value of a child view with the fields in the
+         * screen.
+         *
+         * <p>When multiple transformations are added for the same child view, they are applied
+         * in the same order as added.
+         *
+         * <p><b>Note:</b> The transformations are applied after the
+         * {@link #updateTemplate(RemoteViews) updates} are applied to the presentation template.
+         *
+         * @param id view id of the children view.
+         * @param transformation an implementation provided by the Android System.
+         * @return this builder.
+         * @throws IllegalArgumentException if {@code transformation} is not a class provided
+         * by the Android System.
+         */
+        public Builder transformChild(int id, @NonNull Transformation transformation) {
+            throwIfDestroyed();
+            Preconditions.checkArgument((transformation instanceof InternalTransformation),
+                    "not provided by Android System: " + transformation);
+            if (mTransformations == null) {
+                mTransformations = new ArrayList<>();
+            }
+            mTransformations.add(new Pair<>(id, (InternalTransformation) transformation));
+            return this;
+        }
+
+        /**
+         * Creates a new {@link BatchUpdates} instance.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called before or no call
+         * to {@link #updateTemplate(RemoteViews)} or {@link #transformChild(int, Transformation)}
+         * has been made.
+         */
+        public BatchUpdates build() {
+            throwIfDestroyed();
+            Preconditions.checkState(mUpdates != null || mTransformations != null,
+                    "must call either updateTemplate() or transformChild() at least once");
+            mDestroyed = true;
+            return new BatchUpdates(this);
+        }
+
+        private void throwIfDestroyed() {
+            if (mDestroyed) {
+                throw new IllegalStateException("Already called #build()");
+            }
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return new StringBuilder("BatchUpdates: [")
+                .append(", transformations=")
+                    .append(mTransformations == null ? "N/A" : mTransformations.size())
+                .append(", updates=").append(mUpdates)
+                .append("]").toString();
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (mTransformations == null) {
+            dest.writeIntArray(null);
+        } else {
+            final int size = mTransformations.size();
+            final int[] ids = new int[size];
+            final InternalTransformation[] values = new InternalTransformation[size];
+            for (int i = 0; i < size; i++) {
+                final Pair<Integer, InternalTransformation> pair = mTransformations.get(i);
+                ids[i] = pair.first;
+                values[i] = pair.second;
+            }
+            dest.writeIntArray(ids);
+            dest.writeParcelableArray(values, flags);
+        }
+        dest.writeParcelable(mUpdates, flags);
+    }
+    public static final @android.annotation.NonNull Parcelable.Creator<BatchUpdates> CREATOR =
+            new Parcelable.Creator<BatchUpdates>() {
+        @Override
+        public BatchUpdates 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 Builder builder = new Builder();
+            final int[] ids = parcel.createIntArray();
+            if (ids != null) {
+                final InternalTransformation[] values =
+                    parcel.readParcelableArray(null, InternalTransformation.class);
+                final int size = ids.length;
+                for (int i = 0; i < size; i++) {
+                    builder.transformChild(ids[i], values[i]);
+                }
+            }
+            final RemoteViews updates = parcel.readParcelable(null);
+            if (updates != null) {
+                builder.updateTemplate(updates);
+            }
+            return builder.build();
+        }
+
+        @Override
+        public BatchUpdates[] newArray(int size) {
+            return new BatchUpdates[size];
+        }
+    };
+}
diff --git a/android/service/autofill/CharSequenceTransformation.java b/android/service/autofill/CharSequenceTransformation.java
new file mode 100644
index 0000000..e3e8844
--- /dev/null
+++ b/android/service/autofill/CharSequenceTransformation.java
@@ -0,0 +1,237 @@
+/*
+ * 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;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.Pair;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.LinkedHashMap;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Replaces a {@link TextView} child of a {@link CustomDescription} with the contents of one or
+ * more regular expressions (regexs).
+ *
+ * <p>When it contains more than one field, the fields that match their regex are added to the
+ * overall transformation result.
+ *
+ * <p>For example, a transformation to mask a credit card number contained in just one field would
+ * be:
+ *
+ * <pre class="prettyprint">
+ * new CharSequenceTransformation
+ *     .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
+ *     .build();
+ * </pre>
+ *
+ * <p>But a transformation that generates a {@code Exp: MM / YYYY} credit expiration date from two
+ * fields (month and year) would be:
+ *
+ * <pre class="prettyprint">
+ * new CharSequenceTransformation
+ *   .Builder(ccExpMonthId, Pattern.compile("^(\\d\\d)$"), "Exp: $1")
+ *   .addField(ccExpYearId, Pattern.compile("^(\\d\\d\\d\\d)$"), " / $1");
+ * </pre>
+ */
+public final class CharSequenceTransformation extends InternalTransformation implements
+        Transformation, Parcelable {
+    private static final String TAG = "CharSequenceTransformation";
+
+    // Must use LinkedHashMap to preserve insertion order.
+    @NonNull private final LinkedHashMap<AutofillId, Pair<Pattern, String>> mFields;
+
+    private CharSequenceTransformation(Builder builder) {
+        mFields = builder.mFields;
+    }
+
+    /** @hide */
+    @Override
+    @TestApi
+    public void apply(@NonNull ValueFinder finder, @NonNull RemoteViews parentTemplate,
+            int childViewId) throws Exception {
+        final StringBuilder converted = new StringBuilder();
+        final int size = mFields.size();
+        if (sDebug) Log.d(TAG, size + " fields on id " + childViewId);
+        for (Entry<AutofillId, Pair<Pattern, String>> entry : mFields.entrySet()) {
+            final AutofillId id = entry.getKey();
+            final Pair<Pattern, String> field = entry.getValue();
+            final String value = finder.findByAutofillId(id);
+            if (value == null) {
+                Log.w(TAG, "No value for id " + id);
+                return;
+            }
+            try {
+                final Matcher matcher = field.first.matcher(value);
+                if (!matcher.find()) {
+                    if (sDebug) Log.d(TAG, "Match for " + field.first + " failed on id " + id);
+                    return;
+                }
+                // replaceAll throws an exception if the subst is invalid
+                final String convertedValue = matcher.replaceAll(field.second);
+                converted.append(convertedValue);
+            } catch (Exception e) {
+                // Do not log full exception to avoid PII leaking
+                Log.w(TAG, "Cannot apply " + field.first.pattern() + "->" + field.second + " to "
+                        + "field with autofill id" + id + ": " + e.getClass());
+                throw e;
+            }
+        }
+        // Cannot log converted, it might have PII
+        Log.d(TAG, "Converting text on child " + childViewId + " to " + converted.length()
+                + "_chars");
+        parentTemplate.setCharSequence(childViewId, "setText", converted);
+    }
+
+    /**
+     * Builder for {@link CharSequenceTransformation} objects.
+     */
+    public static class Builder {
+
+        // Must use LinkedHashMap to preserve insertion order.
+        @NonNull private final LinkedHashMap<AutofillId, Pair<Pattern, String>> mFields =
+                new LinkedHashMap<>();
+        private boolean mDestroyed;
+
+        /**
+         * Creates a new builder and adds the first transformed contents of a field to the overall
+         * result of this transformation.
+         *
+         * @param id id of the screen field.
+         * @param regex regular expression with groups (delimited by {@code (} and {@code (}) that
+         * are used to substitute parts of the value.
+         * @param subst the string that substitutes the matched regex, using {@code $} for
+         * group substitution ({@code $1} for 1st group match, {@code $2} for 2nd, etc).
+         */
+        public Builder(@NonNull AutofillId id, @NonNull Pattern regex, @NonNull String subst) {
+            addField(id, regex, subst);
+        }
+
+        /**
+         * Adds the transformed contents of a field to the overall result of this transformation.
+         *
+         * @param id id of the screen field.
+         * @param regex regular expression with groups (delimited by {@code (} and {@code (}) that
+         * are used to substitute parts of the value.
+         * @param subst the string that substitutes the matched regex, using {@code $} for
+         * group substitution ({@code $1} for 1st group match, {@code $2} for 2nd, etc).
+         *
+         * @return this builder.
+         */
+        public Builder addField(@NonNull AutofillId id, @NonNull Pattern regex,
+                @NonNull String subst) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(id);
+            Preconditions.checkNotNull(regex);
+            Preconditions.checkNotNull(subst);
+
+            mFields.put(id, new Pair<>(regex, subst));
+            return this;
+        }
+
+        /**
+         * Creates a new {@link CharSequenceTransformation} instance.
+         */
+        public CharSequenceTransformation build() {
+            throwIfDestroyed();
+            mDestroyed = true;
+            return new CharSequenceTransformation(this);
+        }
+
+        private void throwIfDestroyed() {
+            Preconditions.checkState(!mDestroyed, "Already called build()");
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "MultipleViewsCharSequenceTransformation: [fields=" + mFields + "]";
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        final int size = mFields.size();
+        final AutofillId[] ids = new AutofillId[size];
+        final Pattern[] regexs = new Pattern[size];
+        final String[] substs = new String[size];
+        Pair<Pattern, String> pair;
+        int i = 0;
+        for (Entry<AutofillId, Pair<Pattern, String>> entry : mFields.entrySet()) {
+            ids[i] = entry.getKey();
+            pair = entry.getValue();
+            regexs[i] = pair.first;
+            substs[i] = pair.second;
+            i++;
+        }
+
+        parcel.writeParcelableArray(ids, flags);
+        parcel.writeSerializable(regexs);
+        parcel.writeStringArray(substs);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<CharSequenceTransformation> CREATOR =
+            new Parcelable.Creator<CharSequenceTransformation>() {
+        @Override
+        public CharSequenceTransformation createFromParcel(Parcel parcel) {
+            final AutofillId[] ids = parcel.readParcelableArray(null, AutofillId.class);
+            final Pattern[] regexs = (Pattern[]) parcel.readSerializable();
+            final String[] substs = parcel.createStringArray();
+
+            // 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 CharSequenceTransformation.Builder builder =
+                    new CharSequenceTransformation.Builder(ids[0], regexs[0], substs[0]);
+
+            final int size = ids.length;
+            for (int i = 1; i < size; i++) {
+                builder.addField(ids[i], regexs[i], substs[i]);
+            }
+            return builder.build();
+        }
+
+        @Override
+        public CharSequenceTransformation[] newArray(int size) {
+            return new CharSequenceTransformation[size];
+        }
+    };
+}
diff --git a/android/service/autofill/CompositeUserData.java b/android/service/autofill/CompositeUserData.java
new file mode 100644
index 0000000..c7dc15a
--- /dev/null
+++ b/android/service/autofill/CompositeUserData.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Holds both a generic and package-specific userData used for
+ * <a href="AutofillService.html#FieldClassification">field classification</a>.
+ *
+ * @hide
+ */
+@TestApi
+public final class CompositeUserData implements FieldClassificationUserData, Parcelable {
+
+    private final UserData mGenericUserData;
+    private final UserData mPackageUserData;
+
+    private final String[] mCategories;
+    private final String[] mValues;
+
+    public CompositeUserData(@Nullable UserData genericUserData,
+            @NonNull UserData packageUserData) {
+        mGenericUserData = genericUserData;
+        mPackageUserData = packageUserData;
+
+        final String[] packageCategoryIds = mPackageUserData.getCategoryIds();
+        final String[] packageValues = mPackageUserData.getValues();
+
+        final ArrayList<String> categoryIds = new ArrayList<>(packageCategoryIds.length);
+        final ArrayList<String> values = new ArrayList<>(packageValues.length);
+
+        Collections.addAll(categoryIds, packageCategoryIds);
+        Collections.addAll(values, packageValues);
+
+        if (mGenericUserData != null) {
+            final String[] genericCategoryIds = mGenericUserData.getCategoryIds();
+            final String[] genericValues = mGenericUserData.getValues();
+            final int size = mGenericUserData.getCategoryIds().length;
+            for (int i = 0; i < size; i++) {
+                if (!categoryIds.contains(genericCategoryIds[i])) {
+                    categoryIds.add(genericCategoryIds[i]);
+                    values.add(genericValues[i]);
+                }
+            }
+        }
+
+        mCategories = new String[categoryIds.size()];
+        categoryIds.toArray(mCategories);
+        mValues = new String[values.size()];
+        values.toArray(mValues);
+    }
+
+    @Nullable
+    @Override
+    public String getFieldClassificationAlgorithm() {
+        final String packageDefaultAlgo = mPackageUserData.getFieldClassificationAlgorithm();
+        if (packageDefaultAlgo != null) {
+            return packageDefaultAlgo;
+        } else {
+            return mGenericUserData == null ? null :
+                    mGenericUserData.getFieldClassificationAlgorithm();
+        }
+    }
+
+    @Override
+    public Bundle getDefaultFieldClassificationArgs() {
+        final Bundle packageDefaultArgs = mPackageUserData.getDefaultFieldClassificationArgs();
+        if (packageDefaultArgs != null) {
+            return packageDefaultArgs;
+        } else {
+            return mGenericUserData == null ? null :
+                    mGenericUserData.getDefaultFieldClassificationArgs();
+        }
+    }
+
+    @Nullable
+    @Override
+    public String getFieldClassificationAlgorithmForCategory(@NonNull String categoryId) {
+        Preconditions.checkNotNull(categoryId);
+        final ArrayMap<String, String> categoryAlgorithms = getFieldClassificationAlgorithms();
+        if (categoryAlgorithms == null || !categoryAlgorithms.containsKey(categoryId)) {
+            return null;
+        }
+        return categoryAlgorithms.get(categoryId);
+    }
+
+    @Override
+    public ArrayMap<String, String> getFieldClassificationAlgorithms() {
+        final ArrayMap<String, String> packageAlgos = mPackageUserData
+                .getFieldClassificationAlgorithms();
+        final ArrayMap<String, String> genericAlgos = mGenericUserData == null ? null :
+                mGenericUserData.getFieldClassificationAlgorithms();
+
+        ArrayMap<String, String> categoryAlgorithms = null;
+        if (packageAlgos != null || genericAlgos != null) {
+            categoryAlgorithms = new ArrayMap<>();
+            if (genericAlgos != null) {
+                categoryAlgorithms.putAll(genericAlgos);
+            }
+            if (packageAlgos != null) {
+                categoryAlgorithms.putAll(packageAlgos);
+            }
+        }
+
+        return categoryAlgorithms;
+    }
+
+    @Override
+    public ArrayMap<String, Bundle> getFieldClassificationArgs() {
+        final ArrayMap<String, Bundle> packageArgs = mPackageUserData.getFieldClassificationArgs();
+        final ArrayMap<String, Bundle> genericArgs = mGenericUserData == null ? null :
+                mGenericUserData.getFieldClassificationArgs();
+
+        ArrayMap<String, Bundle> categoryArgs = null;
+        if (packageArgs != null || genericArgs != null) {
+            categoryArgs = new ArrayMap<>();
+            if (genericArgs != null) {
+                categoryArgs.putAll(genericArgs);
+            }
+            if (packageArgs != null) {
+                categoryArgs.putAll(packageArgs);
+            }
+        }
+
+        return categoryArgs;
+    }
+
+    @Override
+    public String[] getCategoryIds() {
+        return mCategories;
+    }
+
+    @Override
+    public String[] getValues() {
+        return mValues;
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        // OK to print UserData because UserData.toString() is PII-aware
+        final StringBuilder builder = new StringBuilder("genericUserData=")
+                .append(mGenericUserData)
+                .append(", packageUserData=").append(mPackageUserData);
+        return builder.toString();
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeParcelable(mGenericUserData, 0);
+        parcel.writeParcelable(mPackageUserData, 0);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<CompositeUserData> CREATOR =
+            new Parcelable.Creator<CompositeUserData>() {
+                @Override
+                public CompositeUserData 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 UserData genericUserData = parcel.readParcelable(null);
+                    final UserData packageUserData = parcel.readParcelable(null);
+                    return new CompositeUserData(genericUserData, packageUserData);
+                }
+
+                @Override
+                public CompositeUserData[] newArray(int size) {
+                    return new CompositeUserData[size];
+                }
+            };
+}
diff --git a/android/service/autofill/CustomDescription.java b/android/service/autofill/CustomDescription.java
new file mode 100644
index 0000000..e274460
--- /dev/null
+++ b/android/service/autofill/CustomDescription.java
@@ -0,0 +1,478 @@
+/*
+ * 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;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+
+/**
+ * Defines a custom description for the autofill save UI.
+ *
+ * <p>This is useful when the autofill service needs to show a detailed view of what would be saved;
+ * for example, when the screen contains a credit card, it could display a logo of the credit card
+ * bank, the last four digits of the credit card number, and its expiration number.
+ *
+ * <p>A custom description is made of 2 parts:
+ * <ul>
+ *   <li>A {@link RemoteViews presentation template} containing children views.
+ *   <li>{@link Transformation Transformations} to populate the children views.
+ * </ul>
+ *
+ * <p>For the credit card example mentioned above, the (simplified) template would be:
+ *
+ * <pre class="prettyprint">
+ * &lt;LinearLayout&gt;
+ *   &lt;ImageView android:id="@+id/templateccLogo"/&gt;
+ *   &lt;TextView android:id="@+id/templateCcNumber"/&gt;
+ *   &lt;TextView android:id="@+id/templateExpDate"/&gt;
+ * &lt;/LinearLayout&gt;
+ * </pre>
+ *
+ * <p>Which in code translates to:
+ *
+ * <pre class="prettyprint">
+ *   CustomDescription.Builder buider = new Builder(new RemoteViews(pgkName, R.layout.cc_template);
+ * </pre>
+ *
+ * <p>Then the value of each of the 3 children would be changed at runtime based on the the value of
+ * the screen fields and the {@link Transformation Transformations}:
+ *
+ * <pre class="prettyprint">
+ * // Image child - different logo for each bank, based on credit card prefix
+ * builder.addChild(R.id.templateccLogo,
+ *   new ImageTransformation.Builder(ccNumberId)
+ *     .addOption(Pattern.compile("^4815.*$"), R.drawable.ic_credit_card_logo1)
+ *     .addOption(Pattern.compile("^1623.*$"), R.drawable.ic_credit_card_logo2)
+ *     .addOption(Pattern.compile("^42.*$"), R.drawable.ic_credit_card_logo3)
+ *     .build();
+ * // Masked credit card number (as .....LAST_4_DIGITS)
+ * builder.addChild(R.id.templateCcNumber, new CharSequenceTransformation
+ *     .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
+ *     .build();
+ * // Expiration date as MM / YYYY:
+ * builder.addChild(R.id.templateExpDate, new CharSequenceTransformation
+ *     .Builder(ccExpMonthId, Pattern.compile("^(\\d\\d)$"), "Exp: $1")
+ *     .addField(ccExpYearId, Pattern.compile("^(\\d\\d)$"), "/$1")
+ *     .build();
+ * </pre>
+ *
+ * <p>See {@link ImageTransformation}, {@link CharSequenceTransformation} for more info about these
+ * transformations.
+ */
+public final class CustomDescription implements Parcelable {
+
+    private final RemoteViews mPresentation;
+    private final ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+    private final ArrayList<Pair<InternalValidator, BatchUpdates>> mUpdates;
+    private final SparseArray<InternalOnClickAction> mActions;
+
+    private CustomDescription(Builder builder) {
+        mPresentation = builder.mPresentation;
+        mTransformations = builder.mTransformations;
+        mUpdates = builder.mUpdates;
+        mActions = builder.mActions;
+    }
+
+    /** @hide */
+    @Nullable
+    public RemoteViews getPresentation() {
+        return mPresentation;
+    }
+
+    /** @hide */
+    @Nullable
+    public ArrayList<Pair<Integer, InternalTransformation>> getTransformations() {
+        return mTransformations;
+    }
+
+    /** @hide */
+    @Nullable
+    public ArrayList<Pair<InternalValidator, BatchUpdates>> getUpdates() {
+        return mUpdates;
+    }
+
+    /** @hide */
+    @Nullable
+    @TestApi
+    public SparseArray<InternalOnClickAction> getActions() {
+        return mActions;
+    }
+
+    /**
+     * Builder for {@link CustomDescription} objects.
+     */
+    public static class Builder {
+        private final RemoteViews mPresentation;
+
+        private boolean mDestroyed;
+        private ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+        private ArrayList<Pair<InternalValidator, BatchUpdates>> mUpdates;
+        private SparseArray<InternalOnClickAction> mActions;
+
+        /**
+         * Default constructor.
+         *
+         * <p><b>Note:</b> If any child view of presentation triggers a
+         * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent) pending intent
+         * on click}, such {@link PendingIntent} must follow the restrictions below, otherwise
+         * it might not be triggered or the autofill save UI might not be shown when its activity
+         * is finished:
+         * <ul>
+         *   <li>It cannot be created with the {@link PendingIntent#FLAG_IMMUTABLE} flag.
+         *   <li>It must be a PendingIntent for an {@link Activity}.
+         *   <li>The activity must call {@link Activity#finish()} when done.
+         *   <li>The activity should not launch other activities.
+         * </ul>
+         *
+         * @param parentPresentation template presentation with (optional) children views.
+         * @throws NullPointerException if {@code parentPresentation} is null (on Android
+         * {@link android.os.Build.VERSION_CODES#P} or higher).
+         */
+        public Builder(@NonNull RemoteViews parentPresentation) {
+            mPresentation = Preconditions.checkNotNull(parentPresentation);
+        }
+
+        /**
+         * Adds a transformation to replace the value of a child view with the fields in the
+         * screen.
+         *
+         * <p>When multiple transformations are added for the same child view, they will be applied
+         * in the same order as added.
+         *
+         * @param id view id of the children view.
+         * @param transformation an implementation provided by the Android System.
+         *
+         * @return this builder.
+         *
+         * @throws IllegalArgumentException if {@code transformation} is not a class provided
+         * by the Android System.
+         * @throws IllegalStateException if {@link #build()} was already called.
+         */
+        @NonNull
+        public Builder addChild(int id, @NonNull Transformation transformation) {
+            throwIfDestroyed();
+            Preconditions.checkArgument((transformation instanceof InternalTransformation),
+                    "not provided by Android System: " + transformation);
+            if (mTransformations == null) {
+                mTransformations = new ArrayList<>();
+            }
+            mTransformations.add(new Pair<>(id, (InternalTransformation) transformation));
+            return this;
+        }
+
+        /**
+         * Updates the {@link RemoteViews presentation template} when a condition is satisfied by
+         * applying a series of remote view operations. This allows dynamic customization of the
+         * portion of the save UI that is controlled by the autofill service. Such dynamic
+         * customization is based on the content of target views.
+         *
+         * <p>The updates are applied in the sequence they are added, after the
+         * {@link #addChild(int, Transformation) transformations} are applied to the children
+         * views.
+         *
+         * <p>For example, to make children views visible when fields are not empty:
+         *
+         * <pre class="prettyprint">
+         * RemoteViews template = new RemoteViews(pgkName, R.layout.my_full_template);
+         *
+         * Pattern notEmptyPattern = Pattern.compile(".+");
+         * Validator hasAddress = new RegexValidator(addressAutofillId, notEmptyPattern);
+         * Validator hasCcNumber = new RegexValidator(ccNumberAutofillId, notEmptyPattern);
+         *
+         * RemoteViews addressUpdates = new RemoteViews(pgkName, R.layout.my_full_template)
+         * addressUpdates.setViewVisibility(R.id.address, View.VISIBLE);
+         *
+         * // Make address visible
+         * BatchUpdates addressBatchUpdates = new BatchUpdates.Builder()
+         *     .updateTemplate(addressUpdates)
+         *     .build();
+         *
+         * RemoteViews ccUpdates = new RemoteViews(pgkName, R.layout.my_full_template)
+         * ccUpdates.setViewVisibility(R.id.cc_number, View.VISIBLE);
+         *
+         * // Mask credit card number (as .....LAST_4_DIGITS) and make it visible
+         * BatchUpdates ccBatchUpdates = new BatchUpdates.Builder()
+         *     .updateTemplate(ccUpdates)
+         *     .transformChild(R.id.templateCcNumber, new CharSequenceTransformation
+         *                     .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
+         *                     .build())
+         *     .build();
+         *
+         * CustomDescription customDescription = new CustomDescription.Builder(template)
+         *     .batchUpdate(hasAddress, addressBatchUpdates)
+         *     .batchUpdate(hasCcNumber, ccBatchUpdates)
+         *     .build();
+         * </pre>
+         *
+         * <p>Another approach is to add a child first, then apply the transformations. Example:
+         *
+         * <pre class="prettyprint">
+         * RemoteViews template = new RemoteViews(pgkName, R.layout.my_base_template);
+         *
+         * RemoteViews addressPresentation = new RemoteViews(pgkName, R.layout.address)
+         * RemoteViews addressUpdates = new RemoteViews(pgkName, R.layout.my_template)
+         * addressUpdates.addView(R.id.parentId, addressPresentation);
+         * BatchUpdates addressBatchUpdates = new BatchUpdates.Builder()
+         *     .updateTemplate(addressUpdates)
+         *     .build();
+         *
+         * RemoteViews ccPresentation = new RemoteViews(pgkName, R.layout.cc)
+         * RemoteViews ccUpdates = new RemoteViews(pgkName, R.layout.my_template)
+         * ccUpdates.addView(R.id.parentId, ccPresentation);
+         * BatchUpdates ccBatchUpdates = new BatchUpdates.Builder()
+         *     .updateTemplate(ccUpdates)
+         *     .transformChild(R.id.templateCcNumber, new CharSequenceTransformation
+         *                     .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
+         *                     .build())
+         *     .build();
+         *
+         * CustomDescription customDescription = new CustomDescription.Builder(template)
+         *     .batchUpdate(hasAddress, addressBatchUpdates)
+         *     .batchUpdate(hasCcNumber, ccBatchUpdates)
+         *     .build();
+         * </pre>
+         *
+         * @param condition condition used to trigger the updates.
+         * @param updates actions to be applied to the
+         * {@link #Builder(RemoteViews) template presentation} when the condition
+         * is satisfied.
+         *
+         * @return this builder
+         *
+         * @throws IllegalArgumentException if {@code condition} is not a class provided
+         * by the Android System.
+         * @throws IllegalStateException if {@link #build()} was already called.
+         */
+        @NonNull
+        public Builder batchUpdate(@NonNull Validator condition, @NonNull BatchUpdates updates) {
+            throwIfDestroyed();
+            Preconditions.checkArgument((condition instanceof InternalValidator),
+                    "not provided by Android System: " + condition);
+            Preconditions.checkNotNull(updates);
+            if (mUpdates == null) {
+                mUpdates = new ArrayList<>();
+            }
+            mUpdates.add(new Pair<>((InternalValidator) condition, updates));
+            return this;
+        }
+
+        /**
+         * Sets an action to be applied to the {@link RemoteViews presentation template} when the
+         * child view with the given {@code id} is clicked.
+         *
+         * <p>Typically used when the presentation uses a masked field (like {@code ****}) for
+         * sensitive fields like passwords or credit cards numbers, but offers a an icon that the
+         * user can tap to show the value for that field.
+         *
+         * <p>Example:
+         *
+         * <pre class="prettyprint">
+         * customDescriptionBuilder
+         *   .addChild(R.id.password_plain, new CharSequenceTransformation
+         *      .Builder(passwordId, Pattern.compile("^(.*)$"), "$1").build())
+         *   .addOnClickAction(R.id.showIcon, new VisibilitySetterAction
+         *     .Builder(R.id.hideIcon, View.VISIBLE)
+         *     .setVisibility(R.id.showIcon, View.GONE)
+         *     .setVisibility(R.id.password_plain, View.VISIBLE)
+         *     .setVisibility(R.id.password_masked, View.GONE)
+         *     .build())
+         *   .addOnClickAction(R.id.hideIcon, new VisibilitySetterAction
+         *     .Builder(R.id.showIcon, View.VISIBLE)
+         *     .setVisibility(R.id.hideIcon, View.GONE)
+         *     .setVisibility(R.id.password_masked, View.VISIBLE)
+         *     .setVisibility(R.id.password_plain, View.GONE)
+         *     .build());
+         * </pre>
+         *
+         * <p><b>Note:</b> Currently only one action can be applied to a child; if this method
+         * is called multiple times passing the same {@code id}, only the last call will be used.
+         *
+         * @param id resource id of the child view.
+         * @param action action to be performed. Must be an an implementation provided by the
+         * Android System.
+         *
+         * @return this builder
+         *
+         * @throws IllegalArgumentException if {@code action} is not a class provided
+         * by the Android System.
+         * @throws IllegalStateException if {@link #build()} was already called.
+         */
+        @NonNull
+        public Builder addOnClickAction(int id, @NonNull OnClickAction action) {
+            throwIfDestroyed();
+            Preconditions.checkArgument((action instanceof InternalOnClickAction),
+                    "not provided by Android System: " + action);
+            if (mActions == null) {
+                mActions = new SparseArray<InternalOnClickAction>();
+            }
+            mActions.put(id, (InternalOnClickAction) action);
+
+            return this;
+        }
+
+        /**
+         * Creates a new {@link CustomDescription} instance.
+         */
+        @NonNull
+        public CustomDescription build() {
+            throwIfDestroyed();
+            mDestroyed = true;
+            return new CustomDescription(this);
+        }
+
+        private void throwIfDestroyed() {
+            if (mDestroyed) {
+                throw new IllegalStateException("Already called #build()");
+            }
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return new StringBuilder("CustomDescription: [presentation=")
+                .append(mPresentation)
+                .append(", transformations=")
+                    .append(mTransformations == null ? "N/A" : mTransformations.size())
+                .append(", updates=")
+                    .append(mUpdates == null ? "N/A" : mUpdates.size())
+                .append(", actions=")
+                    .append(mActions == null ? "N/A" : mActions.size())
+                .append("]").toString();
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mPresentation, flags);
+        if (mPresentation == null) return;
+
+        if (mTransformations == null) {
+            dest.writeIntArray(null);
+        } else {
+            final int size = mTransformations.size();
+            final int[] ids = new int[size];
+            final InternalTransformation[] values = new InternalTransformation[size];
+            for (int i = 0; i < size; i++) {
+                final Pair<Integer, InternalTransformation> pair = mTransformations.get(i);
+                ids[i] = pair.first;
+                values[i] = pair.second;
+            }
+            dest.writeIntArray(ids);
+            dest.writeParcelableArray(values, flags);
+        }
+        if (mUpdates == null) {
+            dest.writeParcelableArray(null, flags);
+        } else {
+            final int size = mUpdates.size();
+            final InternalValidator[] conditions = new InternalValidator[size];
+            final BatchUpdates[] updates = new BatchUpdates[size];
+
+            for (int i = 0; i < size; i++) {
+                final Pair<InternalValidator, BatchUpdates> pair = mUpdates.get(i);
+                conditions[i] = pair.first;
+                updates[i] = pair.second;
+            }
+            dest.writeParcelableArray(conditions, flags);
+            dest.writeParcelableArray(updates, flags);
+        }
+        if (mActions == null) {
+            dest.writeIntArray(null);
+        } else {
+            final int size = mActions.size();
+            final int[] ids = new int[size];
+            final InternalOnClickAction[] values = new InternalOnClickAction[size];
+            for (int i = 0; i < size; i++) {
+                ids[i] = mActions.keyAt(i);
+                values[i] = mActions.valueAt(i);
+            }
+            dest.writeIntArray(ids);
+            dest.writeParcelableArray(values, flags);
+        }
+    }
+    public static final @android.annotation.NonNull Parcelable.Creator<CustomDescription> CREATOR =
+            new Parcelable.Creator<CustomDescription>() {
+        @Override
+        public CustomDescription 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 RemoteViews parentPresentation = parcel.readParcelable(null);
+            if (parentPresentation == null) return null;
+
+            final Builder builder = new Builder(parentPresentation);
+            final int[] transformationIds = parcel.createIntArray();
+            if (transformationIds != null) {
+                final InternalTransformation[] values =
+                    parcel.readParcelableArray(null, InternalTransformation.class);
+                final int size = transformationIds.length;
+                for (int i = 0; i < size; i++) {
+                    builder.addChild(transformationIds[i], values[i]);
+                }
+            }
+            final InternalValidator[] conditions =
+                    parcel.readParcelableArray(null, InternalValidator.class);
+            if (conditions != null) {
+                final BatchUpdates[] updates = parcel.readParcelableArray(null, BatchUpdates.class);
+                final int size = conditions.length;
+                for (int i = 0; i < size; i++) {
+                    builder.batchUpdate(conditions[i], updates[i]);
+                }
+            }
+            final int[] actionIds = parcel.createIntArray();
+            if (actionIds != null) {
+                final InternalOnClickAction[] values =
+                    parcel.readParcelableArray(null, InternalOnClickAction.class);
+                final int size = actionIds.length;
+                for (int i = 0; i < size; i++) {
+                    builder.addOnClickAction(actionIds[i], values[i]);
+                }
+            }
+            return builder.build();
+        }
+
+        @Override
+        public CustomDescription[] newArray(int size) {
+            return new CustomDescription[size];
+        }
+    };
+}
diff --git a/android/service/autofill/Dataset.java b/android/service/autofill/Dataset.java
new file mode 100644
index 0000000..08aa534
--- /dev/null
+++ b/android/service/autofill/Dataset.java
@@ -0,0 +1,789 @@
+/*
+ * Copyright (C) 2016 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.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.IntentSender;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.regex.Pattern;
+
+/**
+ * <p>A <code>Dataset</code> object represents a group of fields (key / value pairs) used
+ * to autofill parts of a screen.
+ *
+ * <p>For more information about the role of datasets in the autofill workflow, read
+ * <a href="/guide/topics/text/autofill-services">Build autofill services</a> and the
+ * <code><a href="/reference/android/service/autofill/AutofillService">AutofillService</a></code>
+ * documentation.
+ *
+ * <a name="BasicUsage"></a>
+ * <h3>Basic usage</h3>
+ *
+ * <p>In its simplest form, a dataset contains one or more fields (comprised of
+ * an {@link AutofillId id}, a {@link AutofillValue value}, and an optional filter
+ * {@link Pattern regex}); and one or more {@link RemoteViews presentations} for these fields
+ * (each field could have its own {@link RemoteViews presentation}, or use the default
+ * {@link RemoteViews presentation} associated with the whole dataset).
+ *
+ * <p>When an autofill service returns datasets in a {@link FillResponse}
+ * and the screen input is focused in a view that is present in at least one of these datasets,
+ * the Android System displays a UI containing the {@link RemoteViews presentation} of
+ * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a
+ * dataset from the UI, all views in that dataset are autofilled.
+ *
+ * <p>If both the current Input Method and autofill service supports inline suggestions, the Dataset
+ * can be shown by the keyboard as a suggestion. To use this feature, the Dataset should contain
+ * an {@link InlinePresentation} representing how the inline suggestion UI will be rendered.
+ *
+ * <a name="Authentication"></a>
+ * <h3>Dataset authentication</h3>
+ *
+ * <p>In a more sophisticated form, the dataset values can be protected until the user authenticates
+ * the dataset&mdash;in that case, when a dataset is selected by the user, the Android System
+ * launches an intent set by the service to "unlock" the dataset.
+ *
+ * <p>For example, when a data set contains credit card information (such as number,
+ * expiration date, and verification code), you could provide a dataset presentation saying
+ * "Tap to authenticate". Then when the user taps that option, you would launch an activity asking
+ * the user to enter the credit card code, and if the user enters a valid code, you could then
+ * "unlock" the dataset.
+ *
+ * <p>You can also use authenticated datasets to offer an interactive UI for the user. For example,
+ * if the activity being autofilled is an account creation screen, you could use an authenticated
+ * dataset to automatically generate a random password for the user.
+ *
+ * <p>See {@link Dataset.Builder#setAuthentication(IntentSender)} for more details about the dataset
+ * authentication mechanism.
+ *
+ * <a name="Filtering"></a>
+ * <h3>Filtering</h3>
+ * <p>The autofill UI automatically changes which values are shown based on value of the view
+ * anchoring it, following the rules below:
+ * <ol>
+ *   <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not
+ * {@link AutofillValue#isText() text} or is empty, all datasets are shown.
+ *   <li>Datasets that have a filter regex (set through
+ * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern)} or
+ * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}) and whose
+ * regex matches the view's text value converted to lower case are shown.
+ *   <li>Datasets that do not require authentication, have a field value that is
+ * {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts
+ * with the lower case value of the view's text are shown.
+ *   <li>All other datasets are hidden.
+ * </ol>
+ *
+ */
+public final class Dataset implements Parcelable {
+
+    private final ArrayList<AutofillId> mFieldIds;
+    private final ArrayList<AutofillValue> mFieldValues;
+    private final ArrayList<RemoteViews> mFieldPresentations;
+    private final ArrayList<InlinePresentation> mFieldInlinePresentations;
+    private final ArrayList<DatasetFieldFilter> mFieldFilters;
+    private final RemoteViews mPresentation;
+    @Nullable private final InlinePresentation mInlinePresentation;
+    private final IntentSender mAuthentication;
+    @Nullable String mId;
+
+    private Dataset(Builder builder) {
+        mFieldIds = builder.mFieldIds;
+        mFieldValues = builder.mFieldValues;
+        mFieldPresentations = builder.mFieldPresentations;
+        mFieldInlinePresentations = builder.mFieldInlinePresentations;
+        mFieldFilters = builder.mFieldFilters;
+        mPresentation = builder.mPresentation;
+        mInlinePresentation = builder.mInlinePresentation;
+        mAuthentication = builder.mAuthentication;
+        mId = builder.mId;
+    }
+
+    /** @hide */
+    public @Nullable ArrayList<AutofillId> getFieldIds() {
+        return mFieldIds;
+    }
+
+    /** @hide */
+    public @Nullable ArrayList<AutofillValue> getFieldValues() {
+        return mFieldValues;
+    }
+
+    /** @hide */
+    public RemoteViews getFieldPresentation(int index) {
+        final RemoteViews customPresentation = mFieldPresentations.get(index);
+        return customPresentation != null ? customPresentation : mPresentation;
+    }
+
+    /** @hide */
+    @Nullable
+    public InlinePresentation getFieldInlinePresentation(int index) {
+        final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(index);
+        return inlinePresentation != null ? inlinePresentation : mInlinePresentation;
+    }
+
+    /** @hide */
+    @Nullable
+    public DatasetFieldFilter getFilter(int index) {
+        return mFieldFilters.get(index);
+    }
+
+    /** @hide */
+    public @Nullable IntentSender getAuthentication() {
+        return mAuthentication;
+    }
+
+    /** @hide */
+    public boolean isEmpty() {
+        return mFieldIds == null || mFieldIds.isEmpty();
+    }
+
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        final StringBuilder builder = new StringBuilder("Dataset[");
+        if (mId == null) {
+            builder.append("noId");
+        } else {
+            // Cannot disclose id because it could contain PII.
+            builder.append("id=").append(mId.length()).append("_chars");
+        }
+        if (mFieldIds != null) {
+            builder.append(", fieldIds=").append(mFieldIds);
+        }
+        if (mFieldValues != null) {
+            builder.append(", fieldValues=").append(mFieldValues);
+        }
+        if (mFieldPresentations != null) {
+            builder.append(", fieldPresentations=").append(mFieldPresentations.size());
+        }
+        if (mFieldInlinePresentations != null) {
+            builder.append(", fieldInlinePresentations=").append(mFieldInlinePresentations.size());
+        }
+        if (mFieldFilters != null) {
+            builder.append(", fieldFilters=").append(mFieldFilters.size());
+        }
+        if (mPresentation != null) {
+            builder.append(", hasPresentation");
+        }
+        if (mInlinePresentation != null) {
+            builder.append(", hasInlinePresentation");
+        }
+        if (mAuthentication != null) {
+            builder.append(", hasAuthentication");
+        }
+        return builder.append(']').toString();
+    }
+
+    /**
+     * Gets the id of this dataset.
+     *
+     * @return The id of this dataset or {@code null} if not set
+     *
+     * @hide
+     */
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * A builder for {@link Dataset} objects. You must provide at least
+     * one value for a field or set an authentication intent.
+     */
+    public static final class Builder {
+        private ArrayList<AutofillId> mFieldIds;
+        private ArrayList<AutofillValue> mFieldValues;
+        private ArrayList<RemoteViews> mFieldPresentations;
+        private ArrayList<InlinePresentation> mFieldInlinePresentations;
+        private ArrayList<DatasetFieldFilter> mFieldFilters;
+        private RemoteViews mPresentation;
+        @Nullable private InlinePresentation mInlinePresentation;
+        private IntentSender mAuthentication;
+        private boolean mDestroyed;
+        @Nullable private String mId;
+
+        /**
+         * Creates a new builder.
+         *
+         * @param presentation The presentation used to visualize this dataset.
+         */
+        public Builder(@NonNull RemoteViews presentation) {
+            Preconditions.checkNotNull(presentation, "presentation must be non-null");
+            mPresentation = presentation;
+        }
+
+        /**
+         * Creates a new builder.
+         *
+         * <p>Only called by augmented autofill.
+         *
+         * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
+         *              as inline suggestions. If the dataset supports inline suggestions,
+         *              this should not be null.
+         * @hide
+         */
+        @SystemApi
+        @TestApi
+        public Builder(@NonNull InlinePresentation inlinePresentation) {
+            Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null");
+            mInlinePresentation = inlinePresentation;
+        }
+
+        /**
+         * Creates a new builder for a dataset where each field will be visualized independently.
+         *
+         * <p>When using this constructor, fields must be set through
+         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
+         * {@link #setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Sets the {@link InlinePresentation} used to visualize this dataset as inline suggestions.
+         * If the dataset supports inline suggestions this should not be null.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         *
+         * @return this builder.
+         */
+        public @NonNull Builder setInlinePresentation(
+                @NonNull InlinePresentation inlinePresentation) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null");
+            mInlinePresentation = inlinePresentation;
+            return this;
+        }
+
+        /**
+         * Triggers a custom UI before before autofilling the screen with the contents of this
+         * dataset.
+         *
+         * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
+         * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
+         * for examples.
+         *
+         * <p>This method is called when you need to provide an authentication
+         * UI for the data set. For example, when a data set contains credit card information
+         * (such as number, expiration date, and verification code), you can display UI
+         * asking for the verification code before filing in the data. Even if the
+         * data set is completely populated the system will launch the specified authentication
+         * intent and will need your approval to fill it in. Since the data set is "locked"
+         * until the user authenticates it, typically this data set name is masked
+         * (for example, "VISA....1234"). Typically you would want to store the data set
+         * labels non-encrypted and the actual sensitive data encrypted and not in memory.
+         * This allows showing the labels in the UI while involving the user if one of
+         * the items with these labels is chosen. Note that if you use sensitive data as
+         * a label, for example an email address, then it should also be encrypted.</p>
+         *
+         * <p>When a user triggers autofill, the system launches the provided intent
+         * whose extras will have the {@link
+         * android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen content},
+         * and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE client
+         * state}. Once you complete your authentication flow you should set the activity
+         * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated
+         * {@link Dataset dataset} or a fully-populated {@link FillResponse response} by
+         * setting it to the {@link
+         * android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. If you
+         * provide a dataset in the result, it will replace the authenticated dataset and
+         * will be immediately filled in. If you provide a response, it will replace the
+         * current response and the UI will be refreshed. For example, if you provided
+         * credit card information without the CVV for the data set in the {@link FillResponse
+         * response} then the returned data set should contain the CVV entry.
+         *
+         * <p><b>Note:</b> Do not make the provided pending intent
+         * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
+         * platform needs to fill in the authentication arguments.
+         *
+         * @param authentication Intent to an activity with your authentication flow.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         *
+         * @return this builder.
+         *
+         * @see android.app.PendingIntent
+         */
+        public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) {
+            throwIfDestroyed();
+            mAuthentication = authentication;
+            return this;
+        }
+
+        /**
+         * Sets the id for the dataset so its usage can be tracked.
+         *
+         * <p>Dataset usage can be tracked for 2 purposes:
+         *
+         * <ul>
+         *   <li>For statistical purposes, the service can call
+         * {@link AutofillService#getFillEventHistory()} when handling {@link
+         * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
+         * calls.
+         *   <li>For normal autofill workflow, the service can call
+         *   {@link SaveRequest#getDatasetIds()} when handling
+         *   {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} calls.
+         * </ul>
+         *
+         * @param id id for this dataset or {@code null} to unset.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         *
+         * @return this builder.
+         */
+        public @NonNull Builder setId(@Nullable String id) {
+            throwIfDestroyed();
+            mId = id;
+            return this;
+        }
+
+        /**
+         * Sets the value of a field.
+         *
+         * <b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, this method would
+         * throw an {@link IllegalStateException} if this builder was constructed without a
+         * {@link RemoteViews presentation}. Android {@link android.os.Build.VERSION_CODES#P} and
+         * higher removed this restriction because datasets used as an
+         * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT
+         * authentication result} do not need a presentation. But if you don't set the presentation
+         * in the constructor in a dataset that is meant to be shown to the user, the autofill UI
+         * for this field will not be displayed.
+         *
+         * <p><b>Note:</b> On Android {@link android.os.Build.VERSION_CODES#P} and
+         * higher, datasets that require authentication can be also be filtered by passing a
+         * {@link AutofillValue#forText(CharSequence) text value} as the {@code value} parameter.
+         *
+         * @param id id returned by {@link
+         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+         * @param value value to be autofilled. Pass {@code null} if you do not have the value
+         *        but the target view is a logical part of the dataset. For example, if
+         *        the dataset needs authentication and you have no access to the value.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         *
+         * @return this builder.
+         */
+        public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
+            throwIfDestroyed();
+            setLifeTheUniverseAndEverything(id, value, null, null, null);
+            return this;
+        }
+
+        /**
+         * Sets the value of a field, using a custom {@link RemoteViews presentation} to
+         * visualize it.
+         *
+         * <p><b>Note:</b> On Android {@link android.os.Build.VERSION_CODES#P} and
+         * higher, datasets that require authentication can be also be filtered by passing a
+         * {@link AutofillValue#forText(CharSequence) text value} as the  {@code value} parameter.
+         *
+         * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+         * or background color: Autofill on different platforms may have different themes.
+         *
+         * @param id id returned by {@link
+         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+         * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+         *        but the target view is a logical part of the dataset. For example, if
+         *        the dataset needs authentication and you have no access to the value.
+         * @param presentation the presentation used to visualize this field.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         *
+         * @return this builder.
+         */
+        public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
+                @NonNull RemoteViews presentation) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(presentation, "presentation cannot be null");
+            setLifeTheUniverseAndEverything(id, value, presentation, null, null);
+            return this;
+        }
+
+        /**
+         * Sets the value of a field using an <a href="#Filtering">explicit filter</a>.
+         *
+         * <p>This method is typically used when the dataset requires authentication and the service
+         * does not know its value but wants to hide the dataset after the user enters a minimum
+         * number of characters. For example, if the dataset represents a credit card number and the
+         * service does not want to show the "Tap to authenticate" message until the user tapped
+         * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
+         *
+         * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
+         * value it's easier to filter by calling {@link #setValue(AutofillId, AutofillValue)} and
+         * use the value to filter.
+         *
+         * @param id id returned by {@link
+         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+         * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+         *        but the target view is a logical part of the dataset. For example, if
+         *        the dataset needs authentication and you have no access to the value.
+         * @param filter regex used to determine if the dataset should be shown in the autofill UI;
+         *        when {@code null}, it disables filtering on that dataset (this is the recommended
+         *        approach when {@code value} is not {@code null} and field contains sensitive data
+         *        such as passwords).
+         *
+         * @return this builder.
+         * @throws IllegalStateException if the builder was constructed without a
+         *         {@link RemoteViews presentation} or {@link #build()} was already called.
+         */
+        public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
+                @Nullable Pattern filter) {
+            throwIfDestroyed();
+            Preconditions.checkState(mPresentation != null,
+                    "Dataset presentation not set on constructor");
+            setLifeTheUniverseAndEverything(id, value, null, null, new DatasetFieldFilter(filter));
+            return this;
+        }
+
+        /**
+         * Sets the value of a field, using a custom {@link RemoteViews presentation} to
+         * visualize it and a <a href="#Filtering">explicit filter</a>.
+         *
+         * <p>This method is typically used when the dataset requires authentication and the service
+         * does not know its value but wants to hide the dataset after the user enters a minimum
+         * number of characters. For example, if the dataset represents a credit card number and the
+         * service does not want to show the "Tap to authenticate" message until the user tapped
+         * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
+         *
+         * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
+         * value it's easier to filter by calling
+         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter.
+         *
+         * @param id id returned by {@link
+         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+         * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+         *        but the target view is a logical part of the dataset. For example, if
+         *        the dataset needs authentication and you have no access to the value.
+         * @param filter regex used to determine if the dataset should be shown in the autofill UI;
+         *        when {@code null}, it disables filtering on that dataset (this is the recommended
+         *        approach when {@code value} is not {@code null} and field contains sensitive data
+         *        such as passwords).
+         * @param presentation the presentation used to visualize this field.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         *
+         * @return this builder.
+         */
+        public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
+                @Nullable Pattern filter, @NonNull RemoteViews presentation) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(presentation, "presentation cannot be null");
+            setLifeTheUniverseAndEverything(id, value, presentation, null,
+                    new DatasetFieldFilter(filter));
+            return this;
+        }
+
+        /**
+         * Sets the value of a field, using a custom {@link RemoteViews presentation} to
+         * visualize it and an {@link InlinePresentation} to visualize it as an inline suggestion.
+         *
+         * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
+         * value it's easier to filter by calling
+         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter.
+         *
+         * @param id id returned by {@link
+         *        android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+         * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+         *        but the target view is a logical part of the dataset. For example, if
+         *        the dataset needs authentication and you have no access to the value.
+         * @param presentation the presentation used to visualize this field.
+         * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
+         *        as inline suggestions. If the dataset supports inline suggestions,
+         *        this should not be null.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         *
+         * @return this builder.
+         */
+        public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
+                @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(presentation, "presentation cannot be null");
+            Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null");
+            setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null);
+            return this;
+        }
+
+        /**
+         * Sets the value of a field, using a custom {@link RemoteViews presentation} to
+         * visualize it and a <a href="#Filtering">explicit filter</a>, and an
+         * {@link InlinePresentation} to visualize it as an inline suggestion.
+         *
+         * <p>This method is typically used when the dataset requires authentication and the service
+         * does not know its value but wants to hide the dataset after the user enters a minimum
+         * number of characters. For example, if the dataset represents a credit card number and the
+         * service does not want to show the "Tap to authenticate" message until the user tapped
+         * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
+         *
+         * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
+         * value it's easier to filter by calling
+         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter.
+         *
+         * @param id id returned by {@link
+         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+         * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+         *        but the target view is a logical part of the dataset. For example, if
+         *        the dataset needs authentication and you have no access to the value.
+         * @param filter regex used to determine if the dataset should be shown in the autofill UI;
+         *        when {@code null}, it disables filtering on that dataset (this is the recommended
+         *        approach when {@code value} is not {@code null} and field contains sensitive data
+         *        such as passwords).
+         * @param presentation the presentation used to visualize this field.
+         * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
+         *        as inline suggestions. If the dataset supports inline suggestions, this
+         *        should not be null.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         *
+         * @return this builder.
+         */
+        public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
+                @Nullable Pattern filter, @NonNull RemoteViews presentation,
+                @NonNull InlinePresentation inlinePresentation) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(presentation, "presentation cannot be null");
+            Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null");
+            setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
+                    new DatasetFieldFilter(filter));
+            return this;
+        }
+
+        /**
+         * Sets the value of a field with an <a href="#Filtering">explicit filter</a>, and using an
+         * {@link InlinePresentation} to visualize it as an inline suggestion.
+         *
+         * <p>Only called by augmented autofill.
+         *
+         * @param id id returned by {@link
+         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+         * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+         *        but the target view is a logical part of the dataset. For example, if
+         *        the dataset needs authentication and you have no access to the value.
+         * @param filter regex used to determine if the dataset should be shown in the autofill UI;
+         *        when {@code null}, it disables filtering on that dataset (this is the recommended
+         *        approach when {@code value} is not {@code null} and field contains sensitive data
+         *        such as passwords).
+         * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
+         *        as inline suggestions. If the dataset supports inline suggestions, this
+         *        should not be null.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         *
+         * @return this builder.
+         *
+         * @hide
+         */
+        @SystemApi
+        @TestApi
+        public @NonNull Builder setFieldInlinePresentation(@NonNull AutofillId id,
+                @Nullable AutofillValue value, @Nullable Pattern filter,
+                @NonNull InlinePresentation inlinePresentation) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null");
+            setLifeTheUniverseAndEverything(id, value, null, inlinePresentation,
+                    new DatasetFieldFilter(filter));
+            return this;
+        }
+
+        private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
+                @Nullable AutofillValue value, @Nullable RemoteViews presentation,
+                @Nullable InlinePresentation inlinePresentation,
+                @Nullable DatasetFieldFilter filter) {
+            Preconditions.checkNotNull(id, "id cannot be null");
+            if (mFieldIds != null) {
+                final int existingIdx = mFieldIds.indexOf(id);
+                if (existingIdx >= 0) {
+                    mFieldValues.set(existingIdx, value);
+                    mFieldPresentations.set(existingIdx, presentation);
+                    mFieldInlinePresentations.set(existingIdx, inlinePresentation);
+                    mFieldFilters.set(existingIdx, filter);
+                    return;
+                }
+            } else {
+                mFieldIds = new ArrayList<>();
+                mFieldValues = new ArrayList<>();
+                mFieldPresentations = new ArrayList<>();
+                mFieldInlinePresentations = new ArrayList<>();
+                mFieldFilters = new ArrayList<>();
+            }
+            mFieldIds.add(id);
+            mFieldValues.add(value);
+            mFieldPresentations.add(presentation);
+            mFieldInlinePresentations.add(inlinePresentation);
+            mFieldFilters.add(filter);
+        }
+
+        /**
+         * Creates a new {@link Dataset} instance.
+         *
+         * <p>You should not interact with this builder once this method is called.
+         *
+         * @throws IllegalStateException if no field was set (through
+         * {@link #setValue(AutofillId, AutofillValue)} or
+         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
+         * {@link #setValue(AutofillId, AutofillValue, RemoteViews, InlinePresentation)}),
+         * or if {@link #build()} was already called.
+         *
+         * @return The built dataset.
+         */
+        public @NonNull Dataset build() {
+            throwIfDestroyed();
+            mDestroyed = true;
+            if (mFieldIds == null) {
+                throw new IllegalStateException("at least one value must be set");
+            }
+            return new Dataset(this);
+        }
+
+        private void throwIfDestroyed() {
+            if (mDestroyed) {
+                throw new IllegalStateException("Already called #build()");
+            }
+        }
+    }
+
+    /////////////////////////////////////
+    //  Parcelable "contract" methods. //
+    /////////////////////////////////////
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeParcelable(mPresentation, flags);
+        parcel.writeParcelable(mInlinePresentation, flags);
+        parcel.writeTypedList(mFieldIds, flags);
+        parcel.writeTypedList(mFieldValues, flags);
+        parcel.writeTypedList(mFieldPresentations, flags);
+        parcel.writeTypedList(mFieldInlinePresentations, flags);
+        parcel.writeTypedList(mFieldFilters, flags);
+        parcel.writeParcelable(mAuthentication, flags);
+        parcel.writeString(mId);
+    }
+
+    public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
+        @Override
+        public Dataset 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 RemoteViews presentation = parcel.readParcelable(null);
+            final InlinePresentation inlinePresentation = parcel.readParcelable(null);
+            final Builder builder = presentation != null
+                    ? inlinePresentation == null
+                            ? new Builder(presentation)
+                            : new Builder(presentation).setInlinePresentation(inlinePresentation)
+                    : inlinePresentation == null
+                            ? new Builder()
+                            : new Builder(inlinePresentation);
+            final ArrayList<AutofillId> ids =
+                    parcel.createTypedArrayList(AutofillId.CREATOR);
+            final ArrayList<AutofillValue> values =
+                    parcel.createTypedArrayList(AutofillValue.CREATOR);
+            final ArrayList<RemoteViews> presentations =
+                    parcel.createTypedArrayList(RemoteViews.CREATOR);
+            final ArrayList<InlinePresentation> inlinePresentations =
+                    parcel.createTypedArrayList(InlinePresentation.CREATOR);
+            final ArrayList<DatasetFieldFilter> filters =
+                    parcel.createTypedArrayList(DatasetFieldFilter.CREATOR);
+            final int inlinePresentationsSize = inlinePresentations.size();
+            for (int i = 0; i < ids.size(); i++) {
+                final AutofillId id = ids.get(i);
+                final AutofillValue value = values.get(i);
+                final RemoteViews fieldPresentation = presentations.get(i);
+                final InlinePresentation fieldInlinePresentation =
+                        i < inlinePresentationsSize ? inlinePresentations.get(i) : null;
+                final DatasetFieldFilter filter = filters.get(i);
+                builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation,
+                        fieldInlinePresentation, filter);
+            }
+            builder.setAuthentication(parcel.readParcelable(null));
+            builder.setId(parcel.readString());
+            return builder.build();
+        }
+
+        @Override
+        public Dataset[] newArray(int size) {
+            return new Dataset[size];
+        }
+    };
+
+    /**
+     * Helper class used to indicate when the service explicitly set a {@link Pattern} filter for a
+     * dataset field&dash; we cannot use a {@link Pattern} directly because then we wouldn't be
+     * able to differentiate whether the service explicitly passed a {@code null} filter to disable
+     * filter, or when it called the methods that does not take a filter {@link Pattern}.
+     *
+     * @hide
+     */
+    public static final class DatasetFieldFilter implements Parcelable {
+
+        @Nullable
+        public final Pattern pattern;
+
+        private DatasetFieldFilter(@Nullable Pattern pattern) {
+            this.pattern = pattern;
+        }
+
+        @Override
+        public String toString() {
+            if (!sDebug) return super.toString();
+
+            // Cannot log pattern because it could contain PII
+            return pattern == null ? "null" : pattern.pattern().length() + "_chars";
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeSerializable(pattern);
+        }
+
+        @SuppressWarnings("hiding")
+        public static final @android.annotation.NonNull Creator<DatasetFieldFilter> CREATOR =
+                new Creator<DatasetFieldFilter>() {
+
+            @Override
+            public DatasetFieldFilter createFromParcel(Parcel parcel) {
+                return new DatasetFieldFilter((Pattern) parcel.readSerializable());
+            }
+
+            @Override
+            public DatasetFieldFilter[] newArray(int size) {
+                return new DatasetFieldFilter[size];
+            }
+        };
+    }
+}
diff --git a/android/service/autofill/DateTransformation.java b/android/service/autofill/DateTransformation.java
new file mode 100644
index 0000000..338ba74
--- /dev/null
+++ b/android/service/autofill/DateTransformation.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.icu.text.DateFormat;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Date;
+
+/**
+ * Replaces a {@link TextView} child of a {@link CustomDescription} with the contents of a field
+ * that is expected to have a {@link AutofillValue#forDate(long) date value}.
+ *
+ * <p>For example, a transformation to display a credit card expiration date as month/year would be:
+ *
+ * <pre class="prettyprint">
+ * new DateTransformation(ccExpDate, new java.text.SimpleDateFormat("MM/yyyy")
+ * </pre>
+ */
+public final class DateTransformation extends InternalTransformation implements
+        Transformation, Parcelable {
+    private static final String TAG = "DateTransformation";
+
+    private final AutofillId mFieldId;
+    private final DateFormat mDateFormat;
+
+    /**
+     * Creates a new transformation.
+     *
+     * @param id id of the screen field.
+     * @param dateFormat object used to transform the date value of the field to a String.
+     */
+    public DateTransformation(@NonNull AutofillId id, @NonNull DateFormat dateFormat) {
+        mFieldId = Preconditions.checkNotNull(id);
+        mDateFormat = Preconditions.checkNotNull(dateFormat);
+    }
+
+    /** @hide */
+    @Override
+    @TestApi
+    public void apply(@NonNull ValueFinder finder, @NonNull RemoteViews parentTemplate,
+            int childViewId) throws Exception {
+        final AutofillValue value = finder.findRawValueByAutofillId(mFieldId);
+        if (value == null) {
+            Log.w(TAG, "No value for id " + mFieldId);
+            return;
+        }
+        if (!value.isDate()) {
+            Log.w(TAG, "Value for " + mFieldId + " is not date: " + value);
+            return;
+        }
+
+        try {
+            final Date date = new Date(value.getDateValue());
+            final String transformed = mDateFormat.format(date);
+            if (sDebug) Log.d(TAG, "Transformed " + date + " to " + transformed);
+
+            parentTemplate.setCharSequence(childViewId, "setText", transformed);
+        } catch (Exception e) {
+            Log.w(TAG, "Could not apply " + mDateFormat + " to " + value + ": " + e);
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "DateTransformation: [id=" + mFieldId + ", format=" + mDateFormat + "]";
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeParcelable(mFieldId, flags);
+        parcel.writeSerializable(mDateFormat);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<DateTransformation> CREATOR =
+            new Parcelable.Creator<DateTransformation>() {
+        @Override
+        public DateTransformation createFromParcel(Parcel parcel) {
+            return new DateTransformation(parcel.readParcelable(null),
+                    (DateFormat) parcel.readSerializable());
+        }
+
+        @Override
+        public DateTransformation[] newArray(int size) {
+            return new DateTransformation[size];
+        }
+    };
+}
diff --git a/android/service/autofill/DateValueSanitizer.java b/android/service/autofill/DateValueSanitizer.java
new file mode 100644
index 0000000..707bab1
--- /dev/null
+++ b/android/service/autofill/DateValueSanitizer.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.icu.text.DateFormat;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Date;
+
+/**
+ * Sanitizes a date {@link AutofillValue} using a {@link DateFormat}.
+ *
+ * <p>For example, to sanitize a credit card expiration date to just its month and year:
+ *
+ * <pre class="prettyprint">
+ * new DateValueSanitizer(new java.text.SimpleDateFormat("MM/yyyy");
+ * </pre>
+ */
+public final class DateValueSanitizer extends InternalSanitizer implements Sanitizer, Parcelable {
+
+    private static final String TAG = "DateValueSanitizer";
+
+    private final DateFormat mDateFormat;
+
+    /**
+     * Default constructor.
+     *
+     * @param dateFormat date format applied to the actual date value of an input field.
+      */
+    public DateValueSanitizer(@NonNull DateFormat dateFormat) {
+        mDateFormat = Preconditions.checkNotNull(dateFormat);
+    }
+
+    /** @hide */
+    @Override
+    @TestApi
+    @Nullable
+    public AutofillValue sanitize(@NonNull AutofillValue value) {
+        if (value == null) {
+            Log.w(TAG, "sanitize() called with null value");
+            return null;
+        }
+        if (!value.isDate()) {
+            if (sDebug) Log.d(TAG, value + " is not a date");
+            return null;
+        }
+
+        try {
+            final Date date = new Date(value.getDateValue());
+
+            // First convert it to string
+            final String converted = mDateFormat.format(date);
+            if (sDebug) Log.d(TAG, "Transformed " + date + " to " + converted);
+            // Then parse it back to date
+            final Date sanitized = mDateFormat.parse(converted);
+            if (sDebug) Log.d(TAG, "Sanitized to " + sanitized);
+            return AutofillValue.forDate(sanitized.getTime());
+        } catch (Exception e) {
+            Log.w(TAG, "Could not apply " + mDateFormat + " to " + value + ": " + e);
+            return null;
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "DateValueSanitizer: [dateFormat=" + mDateFormat + "]";
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeSerializable(mDateFormat);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<DateValueSanitizer> CREATOR =
+            new Parcelable.Creator<DateValueSanitizer>() {
+        @Override
+        public DateValueSanitizer createFromParcel(Parcel parcel) {
+            return new DateValueSanitizer((DateFormat) parcel.readSerializable());
+        }
+
+        @Override
+        public DateValueSanitizer[] newArray(int size) {
+            return new DateValueSanitizer[size];
+        }
+    };
+}
diff --git a/android/service/autofill/FieldClassification.java b/android/service/autofill/FieldClassification.java
new file mode 100644
index 0000000..5bf56cb
--- /dev/null
+++ b/android/service/autofill/FieldClassification.java
@@ -0,0 +1,166 @@
+/*
+ * 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;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.view.autofill.Helper;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Represents the <a href="AutofillService.html#FieldClassification">field classification</a>
+ * results for a given field.
+ */
+public final class FieldClassification {
+
+    private final ArrayList<Match> mMatches;
+
+    /** @hide */
+    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} (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
+     * classification in the device's health.
+     */
+    @NonNull
+    public List<Match> getMatches() {
+        return mMatches;
+    }
+
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "FieldClassification: " + mMatches;
+    }
+
+    private void writeToParcel(Parcel parcel) {
+        parcel.writeInt(mMatches.size());
+        for (int i = 0; i < mMatches.size(); i++) {
+            mMatches.get(i).writeToParcel(parcel);
+        }
+    }
+
+    private static FieldClassification readFromParcel(Parcel 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);
+    }
+
+    static FieldClassification[] readArrayFromParcel(Parcel parcel) {
+        final int length = parcel.readInt();
+        final FieldClassification[] fcs = new FieldClassification[length];
+        for (int i = 0; i < length; i++) {
+            fcs[i] = readFromParcel(parcel);
+        }
+        return fcs;
+    }
+
+    static void writeArrayToParcel(@NonNull Parcel parcel, @NonNull FieldClassification[] fcs) {
+        parcel.writeInt(fcs.length);
+        for (int i = 0; i < fcs.length; i++) {
+            fcs[i].writeToParcel(parcel);
+        }
+    }
+
+    /**
+     * Represents the score of a {@link UserData} entry for the field.
+     */
+    public static final class Match {
+
+        private final String mCategoryId;
+        private final float mScore;
+
+        /** @hide */
+        public Match(String categoryId, float score) {
+            mCategoryId = Preconditions.checkNotNull(categoryId);
+            mScore = score;
+        }
+
+        /**
+         * Gets the category id of the {@link UserData} entry.
+         */
+        @NonNull
+        public String getCategoryId() {
+            return mCategoryId;
+        }
+
+        /**
+         * Gets a classification score for the value of this field compared to the value of the
+         * {@link UserData} entry.
+         *
+         * <p>The score is based in a comparison of the field value and the user data entry, and it
+         * ranges from {@code 0.0F} to {@code 1.0F}:
+         * <ul>
+         *   <li>{@code 1.0F} represents a full match ({@code 100%}).
+         *   <li>{@code 0.0F} represents a full mismatch ({@code 0%}).
+         *   <li>Any other value is a partial match.
+         * </ul>
+         *
+         * <p>How the score is calculated depends on the
+         * {@link UserData.Builder#setFieldClassificationAlgorithm(String, android.os.Bundle)
+         * algorithm} used.
+         */
+        public float getScore() {
+            return mScore;
+        }
+
+        @Override
+        public String toString() {
+            if (!sDebug) return super.toString();
+
+            final StringBuilder string = new StringBuilder("Match: categoryId=");
+            Helper.appendRedacted(string, mCategoryId);
+            return string.append(", score=").append(mScore).toString();
+        }
+
+        private void writeToParcel(@NonNull Parcel parcel) {
+            parcel.writeString(mCategoryId);
+            parcel.writeFloat(mScore);
+        }
+
+        private static Match readFromParcel(@NonNull Parcel parcel) {
+            return new Match(parcel.readString(), parcel.readFloat());
+        }
+    }
+}
diff --git a/android/service/autofill/FieldClassificationUserData.java b/android/service/autofill/FieldClassificationUserData.java
new file mode 100644
index 0000000..3d6cac4
--- /dev/null
+++ b/android/service/autofill/FieldClassificationUserData.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill;
+
+import android.os.Bundle;
+import android.util.ArrayMap;
+
+/**
+ * Class used to define a generic UserData for field classification
+ *
+ * @hide
+ */
+public interface FieldClassificationUserData {
+    /**
+     * Gets the name of the default algorithm that is used to calculate
+     * {@link FieldClassification.Match#getScore()} match scores}.
+     */
+    String getFieldClassificationAlgorithm();
+
+    /**
+     * Gets the default field classification args.
+     */
+    Bundle getDefaultFieldClassificationArgs();
+
+    /**
+     * Gets the name of the field classification algorithm for a specific category.
+     *
+     * @param categoryId id of the specific category.
+     */
+    String getFieldClassificationAlgorithmForCategory(String categoryId);
+
+    /**
+     * Gets all field classification algorithms for specific categories.
+     */
+    ArrayMap<String, String> getFieldClassificationAlgorithms();
+
+    /**
+     * Gets all field classification args for specific categories.
+     */
+    ArrayMap<String, Bundle> getFieldClassificationArgs();
+
+    /**
+     * Gets all category ids
+     */
+    String[] getCategoryIds();
+
+    /**
+     * Gets all string values for field classification
+     */
+    String[] getValues();
+}
diff --git a/android/service/autofill/FillCallback.java b/android/service/autofill/FillCallback.java
new file mode 100644
index 0000000..06e2896
--- /dev/null
+++ b/android/service/autofill/FillCallback.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2016 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.Nullable;
+import android.app.Activity;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * <p><code>FillCallback</code> handles autofill requests from the {@link AutofillService} into
+ * the {@link Activity} being autofilled.
+ *
+ * <p>To learn about using Autofill services in your app, read
+ * <a href="/guide/topics/text/autofill-services">Build autofill services</a>.
+ */
+public final class FillCallback {
+
+    private static final String TAG = "FillCallback";
+
+    private final IFillCallback mCallback;
+    private final int mRequestId;
+    private boolean mCalled;
+
+    /** @hide */
+    public FillCallback(IFillCallback callback, int requestId) {
+        mCallback = callback;
+        mRequestId = requestId;
+    }
+
+    /**
+     * Notifies the Android System that a fill request
+     * ({@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
+     * FillCallback)}) was successfully fulfilled by the service.
+     *
+     * <p>This method should always be called, even if the service doesn't have the heuristics to
+     * fulfill the request (in which case it should be called with {@code null}).
+     *
+     * <p>See the main {@link AutofillService} documentation for more details and examples.
+     *
+     * @param response autofill information for that activity, or {@code null} when the service
+     * cannot autofill the activity.
+     *
+     * @throws IllegalStateException if this method or {@link #onFailure(CharSequence)} was already
+     * called.
+     */
+    public void onSuccess(@Nullable FillResponse response) {
+        assertNotCalled();
+        mCalled = true;
+
+        if (response != null) {
+            response.setRequestId(mRequestId);
+        }
+
+        try {
+            mCallback.onSuccess(response);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Notifies the Android System that a fill request (
+     * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
+     * FillCallback)}) could not be fulfilled by the service (for example, because the user data was
+     * not available yet), so the request could be retried later.
+     *
+     * <p><b>Note: </b>this method should not be used when the service didn't have the heursitics to
+     * fulfill the request; in this case, the service should call {@link #onSuccess(FillResponse)
+     * onSuccess(null)} instead.
+     *
+     * <p><b>Note: </b>prior to {@link android.os.Build.VERSION_CODES#Q}, this
+     * method was not working as intended and the service should always call
+     * {@link #onSuccess(FillResponse) onSuccess(null)} instead.
+     *
+     * <p><b>Note: </b>for apps targeting {@link android.os.Build.VERSION_CODES#Q} or higher, this
+     * method just logs the message on {@code logcat}; for apps targetting older SDKs, it also
+     * displays the message to user using a {@link android.widget.Toast}. Generally speaking, you
+     * should not display an error to the user if the request failed, unless the request had the
+     * {@link FillRequest#FLAG_MANUAL_REQUEST} flag.
+     *
+     * @param message error message. <b>Note: </b> this message should <b>not</b> contain PII
+     * (Personally Identifiable Information, such as username or email address).
+     *
+     * @throws IllegalStateException if this method or {@link #onSuccess(FillResponse)} was already
+     * called.
+     */
+    public void onFailure(@Nullable CharSequence message) {
+        Log.w(TAG, "onFailure(): " + message);
+        assertNotCalled();
+        mCalled = true;
+        try {
+            mCallback.onFailure(mRequestId, message);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    private void assertNotCalled() {
+        if (mCalled) {
+            throw new IllegalStateException("Already called");
+        }
+    }
+}
diff --git a/android/service/autofill/FillContext.java b/android/service/autofill/FillContext.java
new file mode 100644
index 0000000..8331550
--- /dev/null
+++ b/android/service/autofill/FillContext.java
@@ -0,0 +1,276 @@
+/*
+ * 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;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.SparseIntArray;
+import android.view.autofill.AutofillId;
+
+import com.android.internal.util.DataClass;
+
+import java.util.LinkedList;
+
+/**
+ * This class represents a context for each fill request made via {@link
+ * AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)}.
+ * It contains a snapshot of the UI state, the view ids that were returned by
+ * the {@link AutofillService autofill service} as both required to trigger a save
+ * and optional that can be saved, and the id of the corresponding {@link
+ * FillRequest}.
+ * <p>
+ * This context allows you to inspect the values for the interesting views
+ * in the context they appeared. Also a reference to the corresponding fill
+ * request is useful to store meta-data in the client state bundle passed
+ * to {@link FillResponse.Builder#setClientState(Bundle)} to avoid interpreting
+ * the UI state again while saving.
+ */
+@DataClass(
+        genHiddenConstructor = true,
+        genAidl = false)
+public final class FillContext implements Parcelable {
+
+    /**
+     * The id of the {@link FillRequest fill request} this context
+     * corresponds to. This is useful to associate your custom client
+     * state with every request to avoid reinterpreting the UI when saving
+     * user data.
+     */
+    private final int mRequestId;
+
+    /**
+     * The screen content.
+     */
+    private final @NonNull AssistStructure mStructure;
+
+    /**
+     * The AutofillId of the view that triggered autofill.
+     */
+    private final @NonNull AutofillId mFocusedId;
+
+    /**
+     * Lookup table AutofillId->ViewNode to speed up {@link #findViewNodesByAutofillIds}
+     * This is purely a cache and can be deleted at any time
+     */
+    private transient @Nullable ArrayMap<AutofillId, AssistStructure.ViewNode> mViewNodeLookupTable;
+
+
+    @Override
+    public String toString() {
+        if (!sDebug)  return super.toString();
+
+        return "FillContext [reqId=" + mRequestId + ", focusedId=" + mFocusedId + "]";
+    }
+
+    /**
+     * Finds {@link ViewNode ViewNodes} that have the requested ids.
+     *
+     * @param ids The ids of the node to find.
+     *
+     * @return The nodes indexed in the same way as the ids.
+     *
+     * @hide
+     */
+    @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId[] ids) {
+        final LinkedList<ViewNode> nodesToProcess = new LinkedList<>();
+        final ViewNode[] foundNodes = new AssistStructure.ViewNode[ids.length];
+
+        // Indexes of foundNodes that are not found yet
+        final SparseIntArray missingNodeIndexes = new SparseIntArray(ids.length);
+
+        for (int i = 0; i < ids.length; i++) {
+            if (mViewNodeLookupTable != null) {
+                int lookupTableIndex = mViewNodeLookupTable.indexOfKey(ids[i]);
+
+                if (lookupTableIndex >= 0) {
+                    foundNodes[i] = mViewNodeLookupTable.valueAt(lookupTableIndex);
+                } else {
+                    missingNodeIndexes.put(i, /* ignored */ 0);
+                }
+            } else {
+                missingNodeIndexes.put(i, /* ignored */ 0);
+            }
+        }
+
+        final int numWindowNodes = mStructure.getWindowNodeCount();
+        for (int i = 0; i < numWindowNodes; i++) {
+            nodesToProcess.add(mStructure.getWindowNodeAt(i).getRootViewNode());
+        }
+
+        while (missingNodeIndexes.size() > 0 && !nodesToProcess.isEmpty()) {
+            final ViewNode node = nodesToProcess.removeFirst();
+
+            for (int i = 0; i < missingNodeIndexes.size(); i++) {
+                final int index = missingNodeIndexes.keyAt(i);
+                final AutofillId id = ids[index];
+
+                if (id.equals(node.getAutofillId())) {
+                    foundNodes[index] = node;
+
+                    if (mViewNodeLookupTable == null) {
+                        mViewNodeLookupTable = new ArrayMap<>(ids.length);
+                    }
+
+                    mViewNodeLookupTable.put(id, node);
+
+                    missingNodeIndexes.removeAt(i);
+                    break;
+                }
+            }
+
+            for (int i = 0; i < node.getChildCount(); i++) {
+                nodesToProcess.addLast(node.getChildAt(i));
+            }
+        }
+
+        // Remember which ids could not be resolved to not search for them again the next time
+        for (int i = 0; i < missingNodeIndexes.size(); i++) {
+            if (mViewNodeLookupTable == null) {
+                mViewNodeLookupTable = new ArrayMap<>(missingNodeIndexes.size());
+            }
+
+            mViewNodeLookupTable.put(ids[missingNodeIndexes.keyAt(i)], null);
+        }
+
+        return foundNodes;
+    }
+
+
+
+    // Code below generated by codegen v1.0.0.
+    //
+    // DO NOT MODIFY!
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/FillContext.java
+    //
+    // CHECKSTYLE:OFF Generated code
+
+    /**
+     * Creates a new FillContext.
+     *
+     * @param requestId
+     *   The id of the {@link FillRequest fill request} this context
+     *   corresponds to. This is useful to associate your custom client
+     *   state with every request to avoid reinterpreting the UI when saving
+     *   user data.
+     * @param structure
+     *   The screen content.
+     * @param focusedId
+     *   The AutofillId of the view that triggered autofill.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public FillContext(
+            int requestId,
+            @NonNull AssistStructure structure,
+            @NonNull AutofillId focusedId) {
+        this.mRequestId = requestId;
+        this.mStructure = structure;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mStructure);
+        this.mFocusedId = focusedId;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mFocusedId);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The id of the {@link FillRequest fill request} this context
+     * corresponds to. This is useful to associate your custom client
+     * state with every request to avoid reinterpreting the UI when saving
+     * user data.
+     */
+    @DataClass.Generated.Member
+    public int getRequestId() {
+        return mRequestId;
+    }
+
+    /**
+     * The screen content.
+     */
+    @DataClass.Generated.Member
+    public @NonNull AssistStructure getStructure() {
+        return mStructure;
+    }
+
+    /**
+     * The AutofillId of the view that triggered autofill.
+     */
+    @DataClass.Generated.Member
+    public @NonNull AutofillId getFocusedId() {
+        return mFocusedId;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(mRequestId);
+        dest.writeTypedObject(mStructure, flags);
+        dest.writeTypedObject(mFocusedId, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<FillContext> CREATOR
+            = new Parcelable.Creator<FillContext>() {
+        @Override
+        public FillContext[] newArray(int size) {
+            return new FillContext[size];
+        }
+
+        @Override
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        public FillContext createFromParcel(Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            int requestId = in.readInt();
+            AssistStructure structure = (AssistStructure) in.readTypedObject(AssistStructure.CREATOR);
+            AutofillId focusedId = (AutofillId) in.readTypedObject(AutofillId.CREATOR);
+            return new FillContext(
+                    requestId,
+                    structure,
+                    focusedId);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1565152135263L,
+            codegenVersion = "1.0.0",
+            sourceFile = "frameworks/base/core/java/android/service/autofill/FillContext.java",
+            inputSignatures = "private final  int mRequestId\nprivate final @android.annotation.NonNull android.app.assist.AssistStructure mStructure\nprivate final @android.annotation.NonNull android.view.autofill.AutofillId mFocusedId\nprivate transient @android.annotation.Nullable android.util.ArrayMap<android.view.autofill.AutofillId,android.app.assist.AssistStructure.ViewNode> mViewNodeLookupTable\npublic @java.lang.Override java.lang.String toString()\npublic @android.annotation.NonNull android.app.assist.AssistStructure.ViewNode[] findViewNodesByAutofillIds(android.view.autofill.AutofillId[])\nclass FillContext extends java.lang.Object implements [android.os.Parcelable]\[email protected](genHiddenConstructor=true, genAidl=false)")
+    @Deprecated
+    private void __metadata() {}
+
+}
diff --git a/android/service/autofill/FillEventHistory.java b/android/service/autofill/FillEventHistory.java
new file mode 100644
index 0000000..1cd2d62
--- /dev/null
+++ b/android/service/autofill/FillEventHistory.java
@@ -0,0 +1,580 @@
+/*
+ * 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;
+
+import static android.view.autofill.Helper.sVerbose;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+
+import com.android.internal.util.ArrayUtils;
+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.Set;
+
+/**
+ * Describes what happened after the last
+ * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
+ * call.
+ *
+ * <p>This history is typically used to keep track of previous user actions to optimize further
+ * requests. For example, the service might return email addresses in alphabetical order by
+ * default, but change that order based on the address the user picked on previous requests.
+ *
+ * <p>The history is not persisted over reboots, and it's cleared every time the service
+ * replies to a
+ * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
+ * by calling {@link FillCallback#onSuccess(FillResponse)} or
+ * {@link FillCallback#onFailure(CharSequence)} (if the service doesn't call any of these methods,
+ * 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}.
+     */
+    private final int mSessionId;
+
+    @Nullable private final Bundle mClientState;
+    @Nullable List<Event> mEvents;
+
+    /** @hide */
+    public int getSessionId() {
+        return mSessionId;
+    }
+
+    /**
+     * Returns the client state set in the previous {@link FillResponse}.
+     *
+     * <p><b>Note: </b>the state is associated with the app that was autofilled in the previous
+     * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
+     * , which is not necessary the same app being autofilled now.
+     *
+     * @deprecated use {@link #getEvents()} then {@link Event#getClientState()} instead.
+     */
+    @Deprecated
+    @Nullable public Bundle getClientState() {
+        return mClientState;
+    }
+
+    /**
+     * Returns the events occurred after the latest call to
+     * {@link FillCallback#onSuccess(FillResponse)}.
+     *
+     * @return The list of events or {@code null} if non occurred.
+     */
+    @Nullable public List<Event> getEvents() {
+        return mEvents;
+    }
+
+    /**
+     * @hide
+     */
+    public void addEvent(Event event) {
+        if (mEvents == null) {
+            mEvents = new ArrayList<>(1);
+        }
+        mEvents.add(event);
+    }
+
+    /**
+     * @hide
+     */
+    public FillEventHistory(int sessionId, @Nullable Bundle clientState) {
+        mClientState = clientState;
+        mSessionId = sessionId;
+    }
+
+    @Override
+    public String toString() {
+        return mEvents == null ? "no events" : mEvents.toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeBundle(mClientState);
+        if (mEvents == null) {
+            parcel.writeInt(0);
+        } else {
+            parcel.writeInt(mEvents.size());
+
+            int numEvents = mEvents.size();
+            for (int i = 0; i < numEvents; i++) {
+                Event event = mEvents.get(i);
+                parcel.writeInt(event.mEventType);
+                parcel.writeString(event.mDatasetId);
+                parcel.writeBundle(event.mClientState);
+                parcel.writeStringList(event.mSelectedDatasetIds);
+                parcel.writeArraySet(event.mIgnoredDatasetIds);
+                parcel.writeTypedList(event.mChangedFieldIds);
+                parcel.writeStringList(event.mChangedDatasetIds);
+
+                parcel.writeTypedList(event.mManuallyFilledFieldIds);
+                if (event.mManuallyFilledFieldIds != null) {
+                    final int size = event.mManuallyFilledFieldIds.size();
+                    for (int j = 0; j < size; j++) {
+                        parcel.writeStringList(event.mManuallyFilledDatasetIds.get(j));
+                    }
+                }
+                final AutofillId[] detectedFields = event.mDetectedFieldIds;
+                parcel.writeParcelableArray(detectedFields, flags);
+                if (detectedFields != null) {
+                    FieldClassification.writeArrayToParcel(parcel,
+                            event.mDetectedFieldClassifications);
+                }
+            }
+        }
+    }
+
+    /**
+     * Description of an event that occured after the latest call to
+     * {@link FillCallback#onSuccess(FillResponse)}.
+     */
+    public static final class Event {
+        /**
+         * A dataset was selected. The dataset selected can be read from {@link #getDatasetId()}.
+         *
+         * <p><b>Note: </b>on Android {@link android.os.Build.VERSION_CODES#O}, this event was also
+         * incorrectly reported after a
+         * {@link Dataset.Builder#setAuthentication(IntentSender) dataset authentication} was
+         * selected and the service returned a dataset in the
+         * {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT} of the activity launched from that
+         * {@link IntentSender}. This behavior was fixed on Android
+         * {@link android.os.Build.VERSION_CODES#O_MR1}.
+         */
+        public static final int TYPE_DATASET_SELECTED = 0;
+
+        /**
+         * A {@link Dataset.Builder#setAuthentication(IntentSender) dataset authentication} was
+         * selected. The dataset authenticated can be read from {@link #getDatasetId()}.
+         */
+        public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1;
+
+        /**
+         * A {@link FillResponse.Builder#setAuthentication(android.view.autofill.AutofillId[],
+         * IntentSender, android.widget.RemoteViews) fill response authentication} was selected.
+         */
+        public static final int TYPE_AUTHENTICATION_SELECTED = 2;
+
+        /** A save UI was shown. */
+        public static final int TYPE_SAVE_SHOWN = 3;
+
+        /**
+         * A committed autofill context for which the autofill service provided datasets.
+         *
+         * <p>This event is useful to track:
+         * <ul>
+         *   <li>Which datasets (if any) were selected by the user
+         *       ({@link #getSelectedDatasetIds()}).
+         *   <li>Which datasets (if any) were NOT selected by the user
+         *       ({@link #getIgnoredDatasetIds()}).
+         *   <li>Which fields in the selected datasets were changed by the user after the dataset
+         *       was selected ({@link #getChangedFields()}.
+         *   <li>Which fields match the {@link UserData} set by the service.
+         * </ul>
+         *
+         * <p><b>Note: </b>This event is only generated when:
+         * <ul>
+         *   <li>The autofill context is committed.
+         *   <li>The service provides at least one dataset in the
+         *       {@link FillResponse fill responses} associated with the context.
+         *   <li>The last {@link FillResponse fill responses} associated with the context has the
+         *       {@link FillResponse#FLAG_TRACK_CONTEXT_COMMITED} flag.
+         * </ul>
+         *
+         * <p>See {@link android.view.autofill.AutofillManager} for more information about autofill
+         * contexts.
+         */
+        public static final int TYPE_CONTEXT_COMMITTED = 4;
+
+        /**
+         * A dataset selector was shown.
+         *
+         * <p>This event is fired whenever the autofill UI was presented to the user.</p>
+         */
+        public static final int TYPE_DATASETS_SHOWN = 5;
+
+        /** @hide */
+        @IntDef(prefix = { "TYPE_" }, value = {
+                TYPE_DATASET_SELECTED,
+                TYPE_DATASET_AUTHENTICATION_SELECTED,
+                TYPE_AUTHENTICATION_SELECTED,
+                TYPE_SAVE_SHOWN,
+                TYPE_CONTEXT_COMMITTED,
+                TYPE_DATASETS_SHOWN
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface EventIds{}
+
+        @EventIds private final int mEventType;
+        @Nullable private final String mDatasetId;
+        @Nullable private final Bundle mClientState;
+
+        // Note: mSelectedDatasetIds is stored as List<> instead of Set because Session already
+        // stores it as List
+        @Nullable private final List<String> mSelectedDatasetIds;
+        @Nullable private final ArraySet<String> mIgnoredDatasetIds;
+
+        @Nullable private final ArrayList<AutofillId> mChangedFieldIds;
+        @Nullable private final ArrayList<String> mChangedDatasetIds;
+
+        @Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds;
+        @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds;
+
+        @Nullable private final AutofillId[] mDetectedFieldIds;
+        @Nullable private final FieldClassification[] mDetectedFieldClassifications;
+
+        /**
+         * Returns the type of the event.
+         *
+         * @return The type of the event
+         */
+        public int getType() {
+            return mEventType;
+        }
+
+        /**
+         * Returns the id of dataset the id was on.
+         *
+         * @return The id of dataset, or {@code null} the event is not associated with a dataset.
+         */
+        @Nullable public String getDatasetId() {
+            return mDatasetId;
+        }
+
+        /**
+         * Returns the client state from the {@link FillResponse} used to generate this event.
+         *
+         * <p><b>Note: </b>the state is associated with the app that was autofilled in the previous
+         * {@link
+         * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)},
+         * which is not necessary the same app being autofilled now.
+         */
+        @Nullable public Bundle getClientState() {
+            return mClientState;
+        }
+
+        /**
+         * Returns which datasets were selected by the user.
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         */
+        @NonNull public Set<String> getSelectedDatasetIds() {
+            return mSelectedDatasetIds == null ? Collections.emptySet()
+                    : new ArraySet<>(mSelectedDatasetIds);
+        }
+
+        /**
+         * Returns which datasets were NOT selected by the user.
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         */
+        @NonNull public Set<String> getIgnoredDatasetIds() {
+            return mIgnoredDatasetIds == null ? Collections.emptySet() : mIgnoredDatasetIds;
+        }
+
+        /**
+         * Returns which fields in the selected datasets were changed by the user after the dataset
+         * was selected.
+         *
+         * <p>For example, server provides:
+         *
+         * <pre class="prettyprint">
+         *  FillResponse response = new FillResponse.Builder()
+         *      .addDataset(new Dataset.Builder(presentation1)
+         *          .setId("4815")
+         *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
+         *          .build())
+         *      .addDataset(new Dataset.Builder(presentation2)
+         *          .setId("162342")
+         *          .setValue(passwordId, AutofillValue.forText("D'OH"))
+         *          .build())
+         *      .build();
+         * </pre>
+         *
+         * <p>User select both datasets (for username and password) but after the fields are
+         * autofilled, user changes them to:
+         *
+         * <pre class="prettyprint">
+         *   username = "ElBarto";
+         *   password = "AyCaramba";
+         * </pre>
+         *
+         * <p>Then the result is the following map:
+         *
+         * <pre class="prettyprint">
+         *   usernameId => "4815"
+         *   passwordId => "162342"
+         * </pre>
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         *
+         * @return map map whose key is the id of the change fields, and value is the id of
+         * dataset that has that field and was selected by the user.
+         */
+        @NonNull public Map<AutofillId, String> getChangedFields() {
+            if (mChangedFieldIds == null || mChangedDatasetIds == null) {
+                return Collections.emptyMap();
+            }
+
+            final int size = mChangedFieldIds.size();
+            final ArrayMap<AutofillId, String> changedFields = new ArrayMap<>(size);
+            for (int i = 0; i < size; i++) {
+                changedFields.put(mChangedFieldIds.get(i), mChangedDatasetIds.get(i));
+            }
+            return changedFields;
+        }
+
+        /**
+         * Gets the <a href="AutofillService.html#FieldClassification">field classification</a>
+         * results.
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}, when the
+         * service requested {@link FillResponse.Builder#setFieldClassificationIds(AutofillId...)
+         * field classification}.
+         */
+        @NonNull public Map<AutofillId, FieldClassification> getFieldsClassification() {
+            if (mDetectedFieldIds == null) {
+                return Collections.emptyMap();
+            }
+            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 FieldClassification fc = mDetectedFieldClassifications[i];
+                if (sVerbose) {
+                    Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", fc=" + fc);
+                }
+                map.put(id, fc);
+            }
+            return map;
+        }
+
+        /**
+         * Returns which fields were available on datasets provided by the service but manually
+         * entered by the user.
+         *
+         * <p>For example, server provides:
+         *
+         * <pre class="prettyprint">
+         *  FillResponse response = new FillResponse.Builder()
+         *      .addDataset(new Dataset.Builder(presentation1)
+         *          .setId("4815")
+         *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
+         *          .setValue(passwordId, AutofillValue.forText("AyCaramba"))
+         *          .build())
+         *      .addDataset(new Dataset.Builder(presentation2)
+         *          .setId("162342")
+         *          .setValue(usernameId, AutofillValue.forText("ElBarto"))
+         *          .setValue(passwordId, AutofillValue.forText("D'OH"))
+         *          .build())
+         *      .addDataset(new Dataset.Builder(presentation3)
+         *          .setId("108")
+         *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
+         *          .setValue(passwordId, AutofillValue.forText("D'OH"))
+         *          .build())
+         *      .build();
+         * </pre>
+         *
+         * <p>User doesn't select a dataset but manually enters:
+         *
+         * <pre class="prettyprint">
+         *   username = "MrPlow";
+         *   password = "D'OH";
+         * </pre>
+         *
+         * <p>Then the result is the following map:
+         *
+         * <pre class="prettyprint">
+         *   usernameId => { "4815", "108"}
+         *   passwordId => { "162342", "108" }
+         * </pre>
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         *
+         * @return map map whose key is the id of the manually-entered field, and value is the
+         * ids of the datasets that have that value but were not selected by the user.
+         */
+        @NonNull public Map<AutofillId, Set<String>> getManuallyEnteredField() {
+            if (mManuallyFilledFieldIds == null || mManuallyFilledDatasetIds == null) {
+                return Collections.emptyMap();
+            }
+
+            final int size = mManuallyFilledFieldIds.size();
+            final Map<AutofillId, Set<String>> manuallyFilledFields = new ArrayMap<>(size);
+            for (int i = 0; i < size; i++) {
+                final AutofillId fieldId = mManuallyFilledFieldIds.get(i);
+                final ArrayList<String> datasetIds = mManuallyFilledDatasetIds.get(i);
+                manuallyFilledFields.put(fieldId, new ArraySet<>(datasetIds));
+            }
+            return manuallyFilledFields;
+        }
+
+        /**
+         * Creates a new event.
+         *
+         * @param eventType The type of the event
+         * @param datasetId The dataset the event was on, or {@code null} if the event was on the
+         *                  whole response.
+         * @param clientState The client state associated with the event.
+         * @param selectedDatasetIds The ids of datasets selected by the user.
+         * @param ignoredDatasetIds The ids of datasets NOT select by the user.
+         * @param changedFieldIds The ids of fields changed by the user.
+         * @param changedDatasetIds The ids of the datasets that havd values matching the
+         * respective entry on {@code changedFieldIds}.
+         * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user
+         * and belonged to datasets.
+         * @param manuallyFilledDatasetIds The ids of datasets that had values matching the
+         * respective entry on {@code manuallyFilledFieldIds}.
+         * @param detectedFieldClassifications the field classification matches.
+         *
+         * @throws IllegalArgumentException If the length of {@code changedFieldIds} and
+         * {@code changedDatasetIds} doesn't match.
+         * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and
+         * {@code manuallyFilledDatasetIds} doesn't match.
+         *
+         * @hide
+         */
+        public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState,
+                @Nullable List<String> selectedDatasetIds,
+                @Nullable ArraySet<String> ignoredDatasetIds,
+                @Nullable ArrayList<AutofillId> changedFieldIds,
+                @Nullable ArrayList<String> changedDatasetIds,
+                @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
+                @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
+                @Nullable AutofillId[] detectedFieldIds,
+                @Nullable FieldClassification[] detectedFieldClassifications) {
+            mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_DATASETS_SHOWN,
+                    "eventType");
+            mDatasetId = datasetId;
+            mClientState = clientState;
+            mSelectedDatasetIds = selectedDatasetIds;
+            mIgnoredDatasetIds = ignoredDatasetIds;
+            if (changedFieldIds != null) {
+                Preconditions.checkArgument(!ArrayUtils.isEmpty(changedFieldIds)
+                        && changedDatasetIds != null
+                        && changedFieldIds.size() == changedDatasetIds.size(),
+                        "changed ids must have same length and not be empty");
+            }
+            mChangedFieldIds = changedFieldIds;
+            mChangedDatasetIds = changedDatasetIds;
+            if (manuallyFilledFieldIds != null) {
+                Preconditions.checkArgument(!ArrayUtils.isEmpty(manuallyFilledFieldIds)
+                        && manuallyFilledDatasetIds != null
+                        && manuallyFilledFieldIds.size() == manuallyFilledDatasetIds.size(),
+                        "manually filled ids must have same length and not be empty");
+            }
+            mManuallyFilledFieldIds = manuallyFilledFieldIds;
+            mManuallyFilledDatasetIds = manuallyFilledDatasetIds;
+
+            mDetectedFieldIds = detectedFieldIds;
+            mDetectedFieldClassifications = detectedFieldClassifications;
+        }
+
+        @Override
+        public String toString() {
+            return "FillEvent [datasetId=" + mDatasetId
+                    + ", type=" + mEventType
+                    + ", selectedDatasets=" + mSelectedDatasetIds
+                    + ", ignoredDatasetIds=" + mIgnoredDatasetIds
+                    + ", changedFieldIds=" + mChangedFieldIds
+                    + ", changedDatasetsIds=" + mChangedDatasetIds
+                    + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds
+                    + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds
+                    + ", detectedFieldIds=" + Arrays.toString(mDetectedFieldIds)
+                    + ", detectedFieldClassifications ="
+                        + Arrays.toString(mDetectedFieldClassifications)
+                    + "]";
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<FillEventHistory> CREATOR =
+            new Parcelable.Creator<FillEventHistory>() {
+                @Override
+                public FillEventHistory createFromParcel(Parcel parcel) {
+                    FillEventHistory selection = new FillEventHistory(0, parcel.readBundle());
+
+                    final int numEvents = parcel.readInt();
+                    for (int i = 0; i < numEvents; i++) {
+                        final int eventType = parcel.readInt();
+                        final String datasetId = parcel.readString();
+                        final Bundle clientState = parcel.readBundle();
+                        final ArrayList<String> selectedDatasetIds = parcel.createStringArrayList();
+                        @SuppressWarnings("unchecked")
+                        final ArraySet<String> ignoredDatasets =
+                                (ArraySet<String>) parcel.readArraySet(null);
+                        final ArrayList<AutofillId> changedFieldIds =
+                                parcel.createTypedArrayList(AutofillId.CREATOR);
+                        final ArrayList<String> changedDatasetIds = parcel.createStringArrayList();
+
+                        final ArrayList<AutofillId> manuallyFilledFieldIds =
+                                parcel.createTypedArrayList(AutofillId.CREATOR);
+                        final ArrayList<ArrayList<String>> manuallyFilledDatasetIds;
+                        if (manuallyFilledFieldIds != null) {
+                            final int size = manuallyFilledFieldIds.size();
+                            manuallyFilledDatasetIds = new ArrayList<>(size);
+                            for (int j = 0; j < size; j++) {
+                                manuallyFilledDatasetIds.add(parcel.createStringArrayList());
+                            }
+                        } else {
+                            manuallyFilledDatasetIds = null;
+                        }
+                        final AutofillId[] detectedFieldIds = parcel.readParcelableArray(null,
+                                AutofillId.class);
+                        final FieldClassification[] detectedFieldClassifications =
+                                (detectedFieldIds != null)
+                                ? FieldClassification.readArrayFromParcel(parcel)
+                                : null;
+
+                        selection.addEvent(new Event(eventType, datasetId, clientState,
+                                selectedDatasetIds, ignoredDatasets,
+                                changedFieldIds, changedDatasetIds,
+                                manuallyFilledFieldIds, manuallyFilledDatasetIds,
+                                detectedFieldIds, detectedFieldClassifications));
+                    }
+                    return selection;
+                }
+
+                @Override
+                public FillEventHistory[] newArray(int size) {
+                    return new FillEventHistory[size];
+                }
+            };
+}
diff --git a/android/service/autofill/FillRequest.java b/android/service/autofill/FillRequest.java
new file mode 100644
index 0000000..d94160c
--- /dev/null
+++ b/android/service/autofill/FillRequest.java
@@ -0,0 +1,419 @@
+/*
+ * 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;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents a request to an autofill service
+ * to interpret the screen and provide information to the system which views are
+ * interesting for saving and what are the possible ways to fill the inputs on
+ * the screen if applicable.
+ *
+ * @see AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)
+ */
+@DataClass(
+        genToString = true,
+        genHiddenConstructor = true,
+        genHiddenConstDefs = true)
+public final class FillRequest implements Parcelable {
+
+    /**
+     * Indicates autofill was explicitly requested by the user.
+     *
+     * <p>Users typically make an explicit request to autofill a screen in two situations:
+     * <ul>
+     *   <li>The app disabled autofill (using {@link View#setImportantForAutofill(int)}.
+     *   <li>The service could not figure out how to autofill a screen (but the user knows the
+     *       service has data for that app).
+     * </ul>
+     *
+     * <p>This flag is particularly useful for the second case. For example, the service could offer
+     * a complex UI where the user can map which screen views belong to each user data, or it could
+     * offer a simpler UI where the user picks the data for just the view used to trigger the
+     * request (that would be the view whose
+     * {@link android.app.assist.AssistStructure.ViewNode#isFocused()} method returns {@code true}).
+     *
+     * <p>An explicit autofill request is triggered when the
+     * {@link android.view.autofill.AutofillManager#requestAutofill(View)} or
+     * {@link android.view.autofill.AutofillManager#requestAutofill(View, int, android.graphics.Rect)}
+     * is called. For example, standard {@link android.widget.TextView} views show an
+     * {@code AUTOFILL} option in the overflow menu that triggers such request.
+     */
+    public static final @RequestFlags int FLAG_MANUAL_REQUEST = 0x1;
+
+    /**
+     * Indicates this request was made using
+     * <a href="AutofillService.html#CompatibilityMode">compatibility mode</a>.
+     */
+    public static final @RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST = 0x2;
+
+    /**
+     * Indicates the request came from a password field.
+     *
+     * (TODO: b/141703197) Temporary fix for augmented autofill showing passwords.
+     *
+     * @hide
+     */
+    public static final @RequestFlags int FLAG_PASSWORD_INPUT_TYPE = 0x4;
+
+    /** @hide */
+    public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;
+
+    /**
+     * Gets the unique id of this request.
+     */
+    private final int mId;
+
+    /**
+     * Gets the contexts associated with each previous fill request.
+     *
+     * <p><b>Note:</b> Starting on Android {@link android.os.Build.VERSION_CODES#Q}, it could also
+     * include contexts from requests whose {@link SaveInfo} had the
+     * {@link SaveInfo#FLAG_DELAY_SAVE} flag.
+     */
+    private final @NonNull List<FillContext> mFillContexts;
+
+    /**
+     * Gets the latest client state bundle set by the service in a
+     * {@link FillResponse.Builder#setClientState(Bundle) fill response}.
+     *
+     * <p><b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, only client state
+     * bundles set by {@link FillResponse.Builder#setClientState(Bundle)} were considered. On
+     * Android {@link android.os.Build.VERSION_CODES#P} and higher, bundles set in the result of
+     * an authenticated request through the
+     * {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE} extra are
+     * also considered (and take precedence when set).
+     *
+     * @return The client state.
+     */
+    private final @Nullable Bundle mClientState;
+
+    /**
+     * Gets the flags associated with this request.
+     *
+     * @return any combination of {@link #FLAG_MANUAL_REQUEST} and
+     *         {@link #FLAG_COMPATIBILITY_MODE_REQUEST}.
+     */
+    private final @RequestFlags int mFlags;
+
+    /**
+     * Gets the {@link InlineSuggestionsRequest} associated
+     * with this request.
+     *
+     * <p>Autofill Framework will send a {@code @non-null} {@link InlineSuggestionsRequest} if
+     * currently inline suggestions are supported and can be displayed. If the Autofill service
+     * wants to show inline suggestions, they may return {@link Dataset} with valid
+     * {@link InlinePresentation}.</p>
+     *
+     * <p>The Autofill Service must set supportsInlineSuggestions in its XML to enable support
+     * for inline suggestions.</p>
+     *
+     * @return the suggestionspec
+     */
+    private final @Nullable InlineSuggestionsRequest mInlineSuggestionsRequest;
+
+    private void onConstructed() {
+        Preconditions.checkCollectionElementsNotNull(mFillContexts, "contexts");
+    }
+
+
+
+    // Code below generated by codegen v1.0.15.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/FillRequest.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @IntDef(flag = true, prefix = "FLAG_", value = {
+        FLAG_MANUAL_REQUEST,
+        FLAG_COMPATIBILITY_MODE_REQUEST,
+        FLAG_PASSWORD_INPUT_TYPE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface RequestFlags {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String requestFlagsToString(@RequestFlags int value) {
+        return com.android.internal.util.BitUtils.flagsToString(
+                value, FillRequest::singleRequestFlagsToString);
+    }
+
+    @DataClass.Generated.Member
+    static String singleRequestFlagsToString(@RequestFlags int value) {
+        switch (value) {
+            case FLAG_MANUAL_REQUEST:
+                    return "FLAG_MANUAL_REQUEST";
+            case FLAG_COMPATIBILITY_MODE_REQUEST:
+                    return "FLAG_COMPATIBILITY_MODE_REQUEST";
+            case FLAG_PASSWORD_INPUT_TYPE:
+                    return "FLAG_PASSWORD_INPUT_TYPE";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    /**
+     * Creates a new FillRequest.
+     *
+     * @param id
+     *   Gets the unique id of this request.
+     * @param fillContexts
+     *   Gets the contexts associated with each previous fill request.
+     *
+     *   <p><b>Note:</b> Starting on Android {@link android.os.Build.VERSION_CODES#Q}, it could also
+     *   include contexts from requests whose {@link SaveInfo} had the
+     *   {@link SaveInfo#FLAG_DELAY_SAVE} flag.
+     * @param clientState
+     *   Gets the latest client state bundle set by the service in a
+     *   {@link FillResponse.Builder#setClientState(Bundle) fill response}.
+     *
+     *   <p><b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, only client state
+     *   bundles set by {@link FillResponse.Builder#setClientState(Bundle)} were considered. On
+     *   Android {@link android.os.Build.VERSION_CODES#P} and higher, bundles set in the result of
+     *   an authenticated request through the
+     *   {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE} extra are
+     *   also considered (and take precedence when set).
+     * @param flags
+     *   Gets the flags associated with this request.
+     *
+     *   @return any combination of {@link #FLAG_MANUAL_REQUEST} and
+     *           {@link #FLAG_COMPATIBILITY_MODE_REQUEST}.
+     * @param inlineSuggestionsRequest
+     *   Gets the {@link InlineSuggestionsRequest} associated
+     *   with this request.
+     *
+     *   <p>Autofill Framework will send a {@code @non-null} {@link InlineSuggestionsRequest} if
+     *   currently inline suggestions are supported and can be displayed. If the Autofill service
+     *   wants to show inline suggestions, they may return {@link Dataset} with valid
+     *   {@link InlinePresentation}.</p>
+     *
+     *   <p>The Autofill Service must set supportsInlineSuggestions in its XML to enable support
+     *   for inline suggestions.</p>
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public FillRequest(
+            int id,
+            @NonNull List<FillContext> fillContexts,
+            @Nullable Bundle clientState,
+            @RequestFlags int flags,
+            @Nullable InlineSuggestionsRequest inlineSuggestionsRequest) {
+        this.mId = id;
+        this.mFillContexts = fillContexts;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mFillContexts);
+        this.mClientState = clientState;
+        this.mFlags = flags;
+
+        Preconditions.checkFlagsArgument(
+                mFlags,
+                FLAG_MANUAL_REQUEST
+                        | FLAG_COMPATIBILITY_MODE_REQUEST
+                        | FLAG_PASSWORD_INPUT_TYPE);
+        this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+
+        onConstructed();
+    }
+
+    /**
+     * Gets the unique id of this request.
+     */
+    @DataClass.Generated.Member
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Gets the contexts associated with each previous fill request.
+     *
+     * <p><b>Note:</b> Starting on Android {@link android.os.Build.VERSION_CODES#Q}, it could also
+     * include contexts from requests whose {@link SaveInfo} had the
+     * {@link SaveInfo#FLAG_DELAY_SAVE} flag.
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<FillContext> getFillContexts() {
+        return mFillContexts;
+    }
+
+    /**
+     * Gets the latest client state bundle set by the service in a
+     * {@link FillResponse.Builder#setClientState(Bundle) fill response}.
+     *
+     * <p><b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, only client state
+     * bundles set by {@link FillResponse.Builder#setClientState(Bundle)} were considered. On
+     * Android {@link android.os.Build.VERSION_CODES#P} and higher, bundles set in the result of
+     * an authenticated request through the
+     * {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE} extra are
+     * also considered (and take precedence when set).
+     *
+     * @return The client state.
+     */
+    @DataClass.Generated.Member
+    public @Nullable Bundle getClientState() {
+        return mClientState;
+    }
+
+    /**
+     * Gets the flags associated with this request.
+     *
+     * @return any combination of {@link #FLAG_MANUAL_REQUEST} and
+     *         {@link #FLAG_COMPATIBILITY_MODE_REQUEST}.
+     */
+    @DataClass.Generated.Member
+    public @RequestFlags int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * Gets the {@link InlineSuggestionsRequest} associated
+     * with this request.
+     *
+     * <p>Autofill Framework will send a {@code @non-null} {@link InlineSuggestionsRequest} if
+     * currently inline suggestions are supported and can be displayed. If the Autofill service
+     * wants to show inline suggestions, they may return {@link Dataset} with valid
+     * {@link InlinePresentation}.</p>
+     *
+     * <p>The Autofill Service must set supportsInlineSuggestions in its XML to enable support
+     * for inline suggestions.</p>
+     *
+     * @return the suggestionspec
+     */
+    @DataClass.Generated.Member
+    public @Nullable InlineSuggestionsRequest getInlineSuggestionsRequest() {
+        return mInlineSuggestionsRequest;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "FillRequest { " +
+                "id = " + mId + ", " +
+                "fillContexts = " + mFillContexts + ", " +
+                "clientState = " + mClientState + ", " +
+                "flags = " + requestFlagsToString(mFlags) + ", " +
+                "inlineSuggestionsRequest = " + mInlineSuggestionsRequest +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mClientState != null) flg |= 0x4;
+        if (mInlineSuggestionsRequest != null) flg |= 0x10;
+        dest.writeByte(flg);
+        dest.writeInt(mId);
+        dest.writeParcelableList(mFillContexts, flags);
+        if (mClientState != null) dest.writeBundle(mClientState);
+        dest.writeInt(mFlags);
+        if (mInlineSuggestionsRequest != null) dest.writeTypedObject(mInlineSuggestionsRequest, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ FillRequest(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        int id = in.readInt();
+        List<FillContext> fillContexts = new ArrayList<>();
+        in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
+        Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
+        int flags = in.readInt();
+        InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);
+
+        this.mId = id;
+        this.mFillContexts = fillContexts;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mFillContexts);
+        this.mClientState = clientState;
+        this.mFlags = flags;
+
+        Preconditions.checkFlagsArgument(
+                mFlags,
+                FLAG_MANUAL_REQUEST
+                        | FLAG_COMPATIBILITY_MODE_REQUEST
+                        | FLAG_PASSWORD_INPUT_TYPE);
+        this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+
+        onConstructed();
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<FillRequest> CREATOR
+            = new Parcelable.Creator<FillRequest>() {
+        @Override
+        public FillRequest[] newArray(int size) {
+            return new FillRequest[size];
+        }
+
+        @Override
+        public FillRequest createFromParcel(@NonNull Parcel in) {
+            return new FillRequest(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1588119440090L,
+            codegenVersion = "1.0.15",
+            sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
+            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\[email protected](genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/service/autofill/FillResponse.java b/android/service/autofill/FillResponse.java
new file mode 100644
index 0000000..bc08b84
--- /dev/null
+++ b/android/service/autofill/FillResponse.java
@@ -0,0 +1,863 @@
+/*
+ * Copyright (C) 2016 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.service.autofill.AutofillServiceHelper.assertValid;
+import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.app.Activity;
+import android.content.IntentSender;
+import android.content.pm.ParceledListSlice;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+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.List;
+
+/**
+ * Response for an {@link
+ * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}.
+ *
+ * <p>See the main {@link AutofillService} documentation for more details and examples.
+ */
+public final class FillResponse implements Parcelable {
+
+    /**
+     * Flag used to generate {@link FillEventHistory.Event events} of type
+     * {@link FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}&mdash;if this flag is not passed to
+     * {@link Builder#setFlags(int)}, these events are not generated.
+     */
+    public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1;
+
+    /**
+     * Flag used to change the behavior of {@link FillResponse.Builder#disableAutofill(long)}&mdash;
+     * when this flag is passed to {@link Builder#setFlags(int)}, autofill is disabled only for the
+     * activiy that generated the {@link FillRequest}, not the whole app.
+     */
+    public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+            FLAG_TRACK_CONTEXT_COMMITED,
+            FLAG_DISABLE_ACTIVITY_ONLY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface FillResponseFlags {}
+
+    private final @Nullable ParceledListSlice<Dataset> mDatasets;
+    private final @Nullable SaveInfo mSaveInfo;
+    private final @Nullable Bundle mClientState;
+    private final @Nullable RemoteViews mPresentation;
+    private final @Nullable InlinePresentation mInlinePresentation;
+    private final @Nullable RemoteViews mHeader;
+    private final @Nullable RemoteViews mFooter;
+    private final @Nullable IntentSender mAuthentication;
+    private final @Nullable AutofillId[] mAuthenticationIds;
+    private final @Nullable AutofillId[] mIgnoredIds;
+    private final long mDisableDuration;
+    private final @Nullable AutofillId[] mFieldClassificationIds;
+    private final int mFlags;
+    private int mRequestId;
+    private final @Nullable UserData mUserData;
+    private final @Nullable int[] mCancelIds;
+    private final boolean mSupportsInlineSuggestions;
+
+    private FillResponse(@NonNull Builder builder) {
+        mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
+        mSaveInfo = builder.mSaveInfo;
+        mClientState = builder.mClientState;
+        mPresentation = builder.mPresentation;
+        mInlinePresentation = builder.mInlinePresentation;
+        mHeader = builder.mHeader;
+        mFooter = builder.mFooter;
+        mAuthentication = builder.mAuthentication;
+        mAuthenticationIds = builder.mAuthenticationIds;
+        mIgnoredIds = builder.mIgnoredIds;
+        mDisableDuration = builder.mDisableDuration;
+        mFieldClassificationIds = builder.mFieldClassificationIds;
+        mFlags = builder.mFlags;
+        mRequestId = INVALID_REQUEST_ID;
+        mUserData = builder.mUserData;
+        mCancelIds = builder.mCancelIds;
+        mSupportsInlineSuggestions = builder.mSupportsInlineSuggestions;
+    }
+
+    /** @hide */
+    public @Nullable Bundle getClientState() {
+        return mClientState;
+    }
+
+    /** @hide */
+    public @Nullable List<Dataset> getDatasets() {
+        return (mDatasets != null) ? mDatasets.getList() : null;
+    }
+
+    /** @hide */
+    public @Nullable SaveInfo getSaveInfo() {
+        return mSaveInfo;
+    }
+
+    /** @hide */
+    public @Nullable RemoteViews getPresentation() {
+        return mPresentation;
+    }
+
+    /** @hide */
+    public @Nullable InlinePresentation getInlinePresentation() {
+        return mInlinePresentation;
+    }
+
+    /** @hide */
+    public @Nullable RemoteViews getHeader() {
+        return mHeader;
+    }
+
+    /** @hide */
+    public @Nullable RemoteViews getFooter() {
+        return mFooter;
+    }
+
+    /** @hide */
+    public @Nullable IntentSender getAuthentication() {
+        return mAuthentication;
+    }
+
+    /** @hide */
+    public @Nullable AutofillId[] getAuthenticationIds() {
+        return mAuthenticationIds;
+    }
+
+    /** @hide */
+    public @Nullable AutofillId[] getIgnoredIds() {
+        return mIgnoredIds;
+    }
+
+    /** @hide */
+    public long getDisableDuration() {
+        return mDisableDuration;
+    }
+
+    /** @hide */
+    public @Nullable AutofillId[] getFieldClassificationIds() {
+        return mFieldClassificationIds;
+    }
+
+    /** @hide */
+    public @Nullable UserData getUserData() {
+        return mUserData;
+    }
+
+    /** @hide */
+    @TestApi
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * Associates a {@link FillResponse} to a request.
+     *
+     * <p>Set inside of the {@link FillCallback} code, not the {@link AutofillService}.
+     *
+     * @param requestId The id of the request to associate the response to.
+     *
+     * @hide
+     */
+    public void setRequestId(int requestId) {
+        mRequestId = requestId;
+    }
+
+    /** @hide */
+    public int getRequestId() {
+        return mRequestId;
+    }
+
+    /** @hide */
+    @Nullable
+    public int[] getCancelIds() {
+        return mCancelIds;
+    }
+
+    /** @hide */
+    public boolean supportsInlineSuggestions() {
+        return mSupportsInlineSuggestions;
+    }
+
+    /**
+     * Builder for {@link FillResponse} objects. You must to provide at least
+     * one dataset or set an authentication intent with a presentation view.
+     */
+    public static final class Builder {
+        private ArrayList<Dataset> mDatasets;
+        private SaveInfo mSaveInfo;
+        private Bundle mClientState;
+        private RemoteViews mPresentation;
+        private InlinePresentation mInlinePresentation;
+        private RemoteViews mHeader;
+        private RemoteViews mFooter;
+        private IntentSender mAuthentication;
+        private AutofillId[] mAuthenticationIds;
+        private AutofillId[] mIgnoredIds;
+        private long mDisableDuration;
+        private AutofillId[] mFieldClassificationIds;
+        private int mFlags;
+        private boolean mDestroyed;
+        private UserData mUserData;
+        private int[] mCancelIds;
+        private boolean mSupportsInlineSuggestions;
+
+        /**
+         * Triggers a custom UI before before autofilling the screen with any data set in this
+         * response.
+         *
+         * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
+         * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
+         * for examples.
+         *
+         * <p>This is typically useful when a user interaction is required to unlock their
+         * data vault if you encrypt the data set labels and data set data. It is recommended
+         * to encrypt only the sensitive data and not the data set labels which would allow
+         * auth on the data set level leading to a better user experience. Note that if you
+         * use sensitive data as a label, for example an email address, then it should also
+         * be encrypted. The provided {@link android.app.PendingIntent intent} must be an
+         * {@link Activity} which implements your authentication flow. Also if you provide an auth
+         * intent you also need to specify the presentation view to be shown in the fill UI
+         * for the user to trigger your authentication flow.
+         *
+         * <p>When a user triggers autofill, the system launches the provided intent
+         * whose extras will have the
+         * {@link android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen
+         * content} and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE
+         * client state}. Once you complete your authentication flow you should set the
+         * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and set the
+         * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra
+         * with the fully populated {@link FillResponse response} (or {@code null} if the screen
+         * cannot be autofilled).
+         *
+         * <p>For example, if you provided an empty {@link FillResponse response} because the
+         * user's data was locked and marked that the response needs an authentication then
+         * in the response returned if authentication succeeds you need to provide all
+         * available data sets some of which may need to be further authenticated, for
+         * example a credit card whose CVV needs to be entered.
+         *
+         * <p>If you provide an authentication intent you must also provide a presentation
+         * which is used to visualize visualize the response for triggering the authentication
+         * flow.
+         *
+         * <p><b>Note:</b> Do not make the provided pending intent
+         * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
+         * platform needs to fill in the authentication arguments.
+         *
+         * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+         * or background color: Autofill on different platforms may have different themes.
+         *
+         * @param authentication Intent to an activity with your authentication flow.
+         * @param presentation The presentation to visualize the response.
+         * @param ids id of Views that when focused will display the authentication UI.
+         *
+         * @return This builder.
+         *
+         * @throws IllegalArgumentException if any of the following occurs:
+         * <ul>
+         *   <li>{@code ids} is {@code null}</li>
+         *   <li>{@code ids} is empty</li>
+         *   <li>{@code ids} contains a {@code null} element</li>
+         *   <li>both {@code authentication} and {@code presentation} are {@code null}</li>
+         *   <li>both {@code authentication} and {@code presentation} are non-{@code null}</li>
+         * </ul>
+         *
+         * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a
+         * {@link #setFooter(RemoteViews) footer} are already set for this builder.
+         *
+         * @see android.app.PendingIntent#getIntentSender()
+         */
+        @NonNull
+        public Builder setAuthentication(@NonNull AutofillId[] ids,
+                @Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
+            throwIfDestroyed();
+            throwIfDisableAutofillCalled();
+            if (mHeader != null || mFooter != null) {
+                throw new IllegalStateException("Already called #setHeader() or #setFooter()");
+            }
+
+            if (authentication == null ^ presentation == null) {
+                throw new IllegalArgumentException("authentication and presentation"
+                        + " must be both non-null or null");
+            }
+            mAuthentication = authentication;
+            mPresentation = presentation;
+            mAuthenticationIds = assertValid(ids);
+            return this;
+        }
+
+        /**
+         * Triggers a custom UI before before autofilling the screen with any data set in this
+         * response.
+         *
+         * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
+         * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
+         * for examples.
+         *
+         * <p>This method is similar to
+         * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, but also accepts
+         * an {@link InlinePresentation} presentation which is required for authenticating through
+         * the inline autofill flow.
+         *
+         * <p><b>Note:</b> {@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} does
+         * not work with {@link InlinePresentation}.</p>
+         *
+         * @param authentication Intent to an activity with your authentication flow.
+         * @param presentation The presentation to visualize the response.
+         * @param inlinePresentation The inlinePresentation to visualize the response inline.
+         * @param ids id of Views that when focused will display the authentication UI.
+         *
+         * @return This builder.
+         *
+         * @throws IllegalArgumentException if any of the following occurs:
+         * <ul>
+         *   <li>{@code ids} is {@code null}</li>
+         *   <li>{@code ids} is empty</li>
+         *   <li>{@code ids} contains a {@code null} element</li>
+         *   <li>both {@code authentication} and {@code presentation} are {@code null}</li>
+         *   <li>both {@code authentication} and {@code presentation} are non-{@code null}</li>
+         *   <li>both {@code authentication} and {@code inlinePresentation} are {@code null}</li>
+         *   <li>both {@code authentication} and {@code inlinePresentation} are
+         *   non-{@code null}</li>
+         * </ul>
+         *
+         * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a
+         * {@link #setFooter(RemoteViews) footer} are already set for this builder.
+         *
+         * @see android.app.PendingIntent#getIntentSender()
+         */
+        @NonNull
+        public Builder setAuthentication(@NonNull AutofillId[] ids,
+                @Nullable IntentSender authentication, @Nullable RemoteViews presentation,
+                @Nullable InlinePresentation inlinePresentation) {
+            throwIfDestroyed();
+            throwIfDisableAutofillCalled();
+            if (mHeader != null || mFooter != null) {
+                throw new IllegalStateException("Already called #setHeader() or #setFooter()");
+            }
+
+            if (authentication == null ^ (presentation == null && inlinePresentation == null)) {
+                throw new IllegalArgumentException("authentication and presentation "
+                        + "(dropdown or inline), must be both non-null or null");
+            }
+            mAuthentication = authentication;
+            mPresentation = presentation;
+            mInlinePresentation = inlinePresentation;
+            mAuthenticationIds = assertValid(ids);
+            return this;
+        }
+
+        /**
+         * Specifies views that should not trigger new
+         * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
+         * FillCallback)} requests.
+         *
+         * <p>This is typically used when the service cannot autofill the view; for example, a
+         * text field representing the result of a Captcha challenge.
+         */
+        @NonNull
+        public Builder setIgnoredIds(AutofillId...ids) {
+            throwIfDestroyed();
+            mIgnoredIds = ids;
+            return this;
+        }
+
+        /**
+         * Adds a new {@link Dataset} to this response.
+         *
+         * <p><b>Note: </b> on Android {@link android.os.Build.VERSION_CODES#O}, the total number of
+         * datasets is limited by the Binder transaction size, so it's recommended to keep it
+         * small (in the range of 10-20 at most) and use pagination by adding a fake
+         * {@link Dataset.Builder#setAuthentication(IntentSender) authenticated dataset} at the end
+         * with a presentation string like "Next 10" that would return a new {@link FillResponse}
+         * with the next 10 datasets, and so on. This limitation was lifted on
+         * Android {@link android.os.Build.VERSION_CODES#O_MR1}, although the Binder transaction
+         * size can still be reached if each dataset itself is too big.
+         *
+         * @return This builder.
+         */
+        @NonNull
+        public Builder addDataset(@Nullable Dataset dataset) {
+            throwIfDestroyed();
+            throwIfDisableAutofillCalled();
+            if (dataset == null) {
+                return this;
+            }
+            if (mDatasets == null) {
+                mDatasets = new ArrayList<>();
+            }
+            if (!mDatasets.add(dataset)) {
+                return this;
+            }
+            return this;
+        }
+
+        /**
+         * Sets the {@link SaveInfo} associated with this response.
+         *
+         * @return This builder.
+         */
+        public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) {
+            throwIfDestroyed();
+            throwIfDisableAutofillCalled();
+            mSaveInfo = saveInfo;
+            return this;
+        }
+
+        /**
+         * Sets a bundle with state that is passed to subsequent APIs that manipulate this response.
+         *
+         * <p>You can use this bundle to store intermediate state that is passed to subsequent calls
+         * to {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
+         * FillCallback)} and {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}, and
+         * you can also retrieve it by calling {@link FillEventHistory.Event#getClientState()}.
+         *
+         * <p>If this method is called on multiple {@link FillResponse} objects for the same
+         * screen, just the latest bundle is passed back to the service.
+         *
+         * @param clientState The custom client state.
+         * @return This builder.
+         */
+        @NonNull
+        public Builder setClientState(@Nullable Bundle clientState) {
+            throwIfDestroyed();
+            throwIfDisableAutofillCalled();
+            mClientState = clientState;
+            return this;
+        }
+
+        /**
+         * Sets which fields are used for
+         * <a href="AutofillService.html#FieldClassification">field classification</a>
+         *
+         * <p><b>Note:</b> This method automatically adds the
+         * {@link FillResponse#FLAG_TRACK_CONTEXT_COMMITED} to the {@link #setFlags(int) flags}.
+
+         * @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}.
+         */
+        @NonNull
+        public Builder setFieldClassificationIds(@NonNull AutofillId... ids) {
+            throwIfDestroyed();
+            throwIfDisableAutofillCalled();
+            Preconditions.checkArrayElementsNotNull(ids, "ids");
+            Preconditions.checkArgumentInRange(ids.length, 1,
+                    UserData.getMaxFieldClassificationIdsSize(), "ids length");
+            mFieldClassificationIds = ids;
+            mFlags |= FLAG_TRACK_CONTEXT_COMMITED;
+            return this;
+        }
+
+        /**
+         * Sets flags changing the response behavior.
+         *
+         * @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and
+         * {@link #FLAG_DISABLE_ACTIVITY_ONLY}, or {@code 0}.
+         *
+         * @return This builder.
+         */
+        @NonNull
+        public Builder setFlags(@FillResponseFlags int flags) {
+            throwIfDestroyed();
+            mFlags = Preconditions.checkFlagsArgument(flags,
+                    FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
+            return this;
+        }
+
+        /**
+         * Disables autofill for the app or activity.
+         *
+         * <p>This method is useful to optimize performance in cases where the service knows it
+         * can not autofill an app&mdash;for example, when the service has a list of "blacklisted"
+         * apps such as office suites.
+         *
+         * <p>By default, it disables autofill for all activities in the app, unless the response is
+         * {@link #setFlags(int) flagged} with {@link #FLAG_DISABLE_ACTIVITY_ONLY}.
+         *
+         * <p>Autofill for the app or activity is automatically re-enabled after any of the
+         * following conditions:
+         *
+         * <ol>
+         *   <li>{@code duration} milliseconds have passed.
+         *   <li>The autofill service for the user has changed.
+         *   <li>The device has rebooted.
+         * </ol>
+         *
+         * <p><b>Note:</b> Activities that are running when autofill is re-enabled remain
+         * disabled for autofill until they finish and restart.
+         *
+         * @param duration duration to disable autofill, in milliseconds.
+         *
+         * @return this builder
+         *
+         * @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)}, {@link #setClientState(Bundle)}, or
+         *       {@link #setFieldClassificationIds(AutofillId...)} was already called.
+         */
+        @NonNull
+        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
+                    || mFieldClassificationIds != null || mClientState != null) {
+                throw new IllegalStateException("disableAutofill() must be the only method called");
+            }
+
+            mDisableDuration = duration;
+            return this;
+        }
+
+        /**
+         * Sets a header to be shown as the first element in the list of datasets.
+         *
+         * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset},
+         * otherwise {@link #build()} throws an {@link IllegalStateException}. Similarly, this
+         * method should only be used on {@link FillResponse FillResponses} that do not require
+         * authentication (as the header could have been set directly in the main presentation in
+         * these cases).
+         *
+         * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+         * or background color: Autofill on different platforms may have different themes.
+         *
+         * @param header a presentation to represent the header. This presentation is not clickable
+         * &mdash;calling
+         * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would
+         * have no effect.
+         *
+         * @return this builder
+         *
+         * @throws IllegalStateException if an
+         * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) authentication} was
+         * already set for this builder.
+         */
+        // TODO(b/69796626): make it sticky / update javadoc
+        @NonNull
+        public Builder setHeader(@NonNull RemoteViews header) {
+            throwIfDestroyed();
+            throwIfAuthenticationCalled();
+            mHeader = Preconditions.checkNotNull(header);
+            return this;
+        }
+
+        /**
+         * Sets a footer to be shown as the last element in the list of datasets.
+         *
+         * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset},
+         * otherwise {@link #build()} throws an {@link IllegalStateException}. Similarly, this
+         * method should only be used on {@link FillResponse FillResponses} that do not require
+         * authentication (as the footer could have been set directly in the main presentation in
+         * these cases).
+         *
+         * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+         * or background color: Autofill on different platforms may have different themes.
+         *
+         * @param footer a presentation to represent the footer. This presentation is not clickable
+         * &mdash;calling
+         * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would
+         * have no effect.
+         *
+         * @return this builder
+         *
+         * @throws IllegalStateException if the FillResponse
+         * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)
+         * requires authentication}.
+         */
+        // TODO(b/69796626): make it sticky / update javadoc
+        @NonNull
+        public Builder setFooter(@NonNull RemoteViews footer) {
+            throwIfDestroyed();
+            throwIfAuthenticationCalled();
+            mFooter = Preconditions.checkNotNull(footer);
+            return this;
+        }
+
+        /**
+         * Sets a specific {@link UserData} for field classification for this request only.
+         *
+         * <p>Any fields in this UserData will override corresponding fields in the generic
+         * UserData object
+         *
+         * @return this builder
+         * @throws IllegalStateException if the FillResponse
+         * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)
+         * requires authentication}.
+         */
+        @NonNull
+        public Builder setUserData(@NonNull UserData userData) {
+            throwIfDestroyed();
+            throwIfAuthenticationCalled();
+            mUserData = Preconditions.checkNotNull(userData);
+            return this;
+        }
+
+        /**
+         * Sets target resource IDs of the child view in {@link RemoteViews Presentation Template}
+         * which will cancel the session when clicked.
+         * Those targets will be respectively applied to a child of the header, footer and
+         * each {@link Dataset}.
+         *
+         * @param ids array of the resource id. Empty list or non-existing id has no effect.
+         *
+         * @return this builder
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         */
+        @NonNull
+        public Builder setPresentationCancelIds(@Nullable int[] ids) {
+            throwIfDestroyed();
+            mCancelIds = ids;
+            return this;
+        }
+
+        /**
+         * Builds a new {@link FillResponse} instance.
+         *
+         * @throws IllegalStateException if any of the following conditions occur:
+         * <ol>
+         *   <li>{@link #build()} was already called.
+         *   <li>No call was made to {@link #addDataset(Dataset)},
+         *       {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
+         *       {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)},
+         *       {@link #setClientState(Bundle)},
+         *       or {@link #setFieldClassificationIds(AutofillId...)}.
+         *   <li>{@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} is called
+         *       without any previous calls to {@link #addDataset(Dataset)}.
+         * </ol>
+         *
+         * @return A built response.
+         */
+        @NonNull
+        public FillResponse build() {
+            throwIfDestroyed();
+            if (mAuthentication == null && mDatasets == null && mSaveInfo == 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");
+            }
+            if (mDatasets == null && (mHeader != null || mFooter != null)) {
+                throw new IllegalStateException(
+                        "must add at least 1 dataset when using header or footer");
+            }
+
+            if (mDatasets != null) {
+                for (final Dataset dataset : mDatasets) {
+                    if (dataset.getFieldInlinePresentation(0) != null) {
+                        mSupportsInlineSuggestions = true;
+                        break;
+                    }
+                }
+            } else if (mInlinePresentation != null) {
+                mSupportsInlineSuggestions = true;
+            }
+
+            mDestroyed = true;
+            return new FillResponse(this);
+        }
+
+        private void throwIfDestroyed() {
+            if (mDestroyed) {
+                throw new IllegalStateException("Already called #build()");
+            }
+        }
+
+        private void throwIfDisableAutofillCalled() {
+            if (mDisableDuration > 0) {
+                throw new IllegalStateException("Already called #disableAutofill()");
+            }
+        }
+
+        private void throwIfAuthenticationCalled() {
+            if (mAuthentication != null) {
+                throw new IllegalStateException("Already called #setAuthentication()");
+            }
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        // TODO: create a dump() method instead
+        final StringBuilder builder = new StringBuilder(
+                "FillResponse : [mRequestId=" + mRequestId);
+        if (mDatasets != null) {
+            builder.append(", datasets=").append(mDatasets.getList());
+        }
+        if (mSaveInfo != null) {
+            builder.append(", saveInfo=").append(mSaveInfo);
+        }
+        if (mClientState != null) {
+            builder.append(", hasClientState");
+        }
+        if (mPresentation != null) {
+            builder.append(", hasPresentation");
+        }
+        if (mInlinePresentation != null) {
+            builder.append(", hasInlinePresentation");
+        }
+        if (mHeader != null) {
+            builder.append(", hasHeader");
+        }
+        if (mFooter != null) {
+            builder.append(", hasFooter");
+        }
+        if (mAuthentication != null) {
+            builder.append(", hasAuthentication");
+        }
+        if (mAuthenticationIds != null) {
+            builder.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds));
+        }
+        builder.append(", disableDuration=").append(mDisableDuration);
+        if (mFlags != 0) {
+            builder.append(", flags=").append(mFlags);
+        }
+        if (mFieldClassificationIds != null) {
+            builder.append(Arrays.toString(mFieldClassificationIds));
+        }
+        if (mUserData != null) {
+            builder.append(", userData=").append(mUserData);
+        }
+        if (mCancelIds != null) {
+            builder.append(", mCancelIds=").append(mCancelIds.length);
+        }
+        builder.append(", mSupportInlinePresentations=").append(mSupportsInlineSuggestions);
+        return builder.append("]").toString();
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeParcelable(mDatasets, flags);
+        parcel.writeParcelable(mSaveInfo, flags);
+        parcel.writeParcelable(mClientState, flags);
+        parcel.writeParcelableArray(mAuthenticationIds, flags);
+        parcel.writeParcelable(mAuthentication, flags);
+        parcel.writeParcelable(mPresentation, flags);
+        parcel.writeParcelable(mInlinePresentation, flags);
+        parcel.writeParcelable(mHeader, flags);
+        parcel.writeParcelable(mFooter, flags);
+        parcel.writeParcelable(mUserData, flags);
+        parcel.writeParcelableArray(mIgnoredIds, flags);
+        parcel.writeLong(mDisableDuration);
+        parcel.writeParcelableArray(mFieldClassificationIds, flags);
+        parcel.writeInt(mFlags);
+        parcel.writeIntArray(mCancelIds);
+        parcel.writeInt(mRequestId);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<FillResponse> CREATOR =
+            new Parcelable.Creator<FillResponse>() {
+        @Override
+        public FillResponse 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 Builder builder = new Builder();
+            final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null);
+            final List<Dataset> datasets = (datasetSlice != null) ? datasetSlice.getList() : null;
+            final int datasetCount = (datasets != null) ? datasets.size() : 0;
+            for (int i = 0; i < datasetCount; i++) {
+                builder.addDataset(datasets.get(i));
+            }
+            builder.setSaveInfo(parcel.readParcelable(null));
+            builder.setClientState(parcel.readParcelable(null));
+
+            // Sets authentication state.
+            final AutofillId[] authenticationIds = parcel.readParcelableArray(null,
+                    AutofillId.class);
+            final IntentSender authentication = parcel.readParcelable(null);
+            final RemoteViews presentation = parcel.readParcelable(null);
+            final InlinePresentation inlinePresentation = parcel.readParcelable(null);
+            if (authenticationIds != null) {
+                builder.setAuthentication(authenticationIds, authentication, presentation,
+                        inlinePresentation);
+            }
+            final RemoteViews header = parcel.readParcelable(null);
+            if (header != null) {
+                builder.setHeader(header);
+            }
+            final RemoteViews footer = parcel.readParcelable(null);
+            if (footer != null) {
+                builder.setFooter(footer);
+            }
+            final UserData userData = parcel.readParcelable(null);
+            if (userData != null) {
+                builder.setUserData(userData);
+            }
+
+            builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class));
+            final long disableDuration = parcel.readLong();
+            if (disableDuration > 0) {
+                builder.disableAutofill(disableDuration);
+            }
+            final AutofillId[] fieldClassifactionIds =
+                    parcel.readParcelableArray(null, AutofillId.class);
+            if (fieldClassifactionIds != null) {
+                builder.setFieldClassificationIds(fieldClassifactionIds);
+            }
+            builder.setFlags(parcel.readInt());
+            final int[] cancelIds = parcel.createIntArray();
+            builder.setPresentationCancelIds(cancelIds);
+
+            final FillResponse response = builder.build();
+            response.setRequestId(parcel.readInt());
+
+            return response;
+        }
+
+        @Override
+        public FillResponse[] newArray(int size) {
+            return new FillResponse[size];
+        }
+    };
+}
diff --git a/android/service/autofill/ImageTransformation.java b/android/service/autofill/ImageTransformation.java
new file mode 100644
index 0000000..974f0ea
--- /dev/null
+++ b/android/service/autofill/ImageTransformation.java
@@ -0,0 +1,291 @@
+/*
+ * 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;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.widget.ImageView;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.regex.Pattern;
+
+/**
+ * Replaces the content of a child {@link ImageView} of a
+ * {@link RemoteViews presentation template} with the first image that matches a regular expression
+ * (regex).
+ *
+ * <p>Typically used to display credit card logos. Example:
+ *
+ * <pre class="prettyprint">
+ *   new ImageTransformation.Builder(ccNumberId, Pattern.compile("^4815.*$"),
+ *                                   R.drawable.ic_credit_card_logo1, "Brand 1")
+ *     .addOption(Pattern.compile("^1623.*$"), R.drawable.ic_credit_card_logo2, "Brand 2")
+ *     .addOption(Pattern.compile("^42.*$"), R.drawable.ic_credit_card_logo3, "Brand 3")
+ *     .build();
+ * </pre>
+ *
+ * <p>There is no imposed limit in the number of options, but keep in mind that regexs are
+ * expensive to evaluate, so use the minimum number of regexs and add the most common first
+ * (for example, if this is a tranformation for a credit card logo and the most common credit card
+ * issuers are banks X and Y, add the regexes that resolves these 2 banks first).
+ */
+public final class ImageTransformation extends InternalTransformation implements Transformation,
+        Parcelable {
+    private static final String TAG = "ImageTransformation";
+
+    private final AutofillId mId;
+    private final ArrayList<Option> mOptions;
+
+    private ImageTransformation(Builder builder) {
+        mId = builder.mId;
+        mOptions = builder.mOptions;
+    }
+
+    /** @hide */
+    @TestApi
+    @Override
+    public void apply(@NonNull ValueFinder finder, @NonNull RemoteViews parentTemplate,
+            int childViewId) throws Exception {
+        final String value = finder.findByAutofillId(mId);
+        if (value == null) {
+            Log.w(TAG, "No view for id " + mId);
+            return;
+        }
+        final int size = mOptions.size();
+        if (sDebug) {
+            Log.d(TAG, size + " multiple options on id " + childViewId + " to compare against");
+        }
+
+        for (int i = 0; i < size; i++) {
+            final Option option = mOptions.get(i);
+            try {
+                if (option.pattern.matcher(value).matches()) {
+                    Log.d(TAG, "Found match at " + i + ": " + option);
+                    parentTemplate.setImageViewResource(childViewId, option.resId);
+                    if (option.contentDescription != null) {
+                        parentTemplate.setContentDescription(childViewId,
+                                option.contentDescription);
+                    }
+                    return;
+                }
+            } catch (Exception e) {
+                // Do not log full exception to avoid PII leaking
+                Log.w(TAG, "Error matching regex #" + i + "(" + option.pattern + ") on id "
+                        + option.resId + ": " + e.getClass());
+                throw e;
+
+            }
+        }
+        if (sDebug) Log.d(TAG, "No match for " + value);
+    }
+
+    /**
+     * Builder for {@link ImageTransformation} objects.
+     */
+    public static class Builder {
+        private final AutofillId mId;
+        private final ArrayList<Option> mOptions = new ArrayList<>();
+        private boolean mDestroyed;
+
+        /**
+         * Creates a new builder for a autofill id and add a first option.
+         *
+         * @param id id of the screen field that will be used to evaluate whether the image should
+         * be used.
+         * @param regex regular expression defining what should be matched to use this image.
+         * @param resId resource id of the image (in the autofill service's package). The
+         * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
+         *
+         * @deprecated use
+         * {@link #Builder(AutofillId, Pattern, int, CharSequence)} instead.
+         */
+        @Deprecated
+        public Builder(@NonNull AutofillId id, @NonNull Pattern regex, @DrawableRes int resId) {
+            mId = Preconditions.checkNotNull(id);
+            addOption(regex, resId);
+        }
+
+        /**
+         * Creates a new builder for a autofill id and add a first option.
+         *
+         * @param id id of the screen field that will be used to evaluate whether the image should
+         * be used.
+         * @param regex regular expression defining what should be matched to use this image.
+         * @param resId resource id of the image (in the autofill service's package). The
+         * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
+         * @param contentDescription content description to be applied in the child view.
+         */
+        public Builder(@NonNull AutofillId id, @NonNull Pattern regex, @DrawableRes int resId,
+                @NonNull CharSequence contentDescription) {
+            mId = Preconditions.checkNotNull(id);
+            addOption(regex, resId, contentDescription);
+        }
+
+        /**
+         * Adds an option to replace the child view with a different image when the regex matches.
+         *
+         * @param regex regular expression defining what should be matched to use this image.
+         * @param resId resource id of the image (in the autofill service's package). The
+         * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
+         *
+         * @return this build
+         *
+         * @deprecated use {@link #addOption(Pattern, int, CharSequence)} instead.
+         */
+        @Deprecated
+        public Builder addOption(@NonNull Pattern regex, @DrawableRes int resId) {
+            addOptionInternal(regex, resId, null);
+            return this;
+        }
+
+        /**
+         * Adds an option to replace the child view with a different image and content description
+         * when the regex matches.
+         *
+         * @param regex regular expression defining what should be matched to use this image.
+         * @param resId resource id of the image (in the autofill service's package). The
+         * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
+         * @param contentDescription content description to be applied in the child view.
+         *
+         * @return this build
+         */
+        public Builder addOption(@NonNull Pattern regex, @DrawableRes int resId,
+                @NonNull CharSequence contentDescription) {
+            addOptionInternal(regex, resId, Preconditions.checkNotNull(contentDescription));
+            return this;
+        }
+
+        private void addOptionInternal(@NonNull Pattern regex, @DrawableRes int resId,
+                @Nullable CharSequence contentDescription) {
+            throwIfDestroyed();
+
+            Preconditions.checkNotNull(regex);
+            Preconditions.checkArgument(resId != 0);
+
+            mOptions.add(new Option(regex, resId, contentDescription));
+        }
+
+
+        /**
+         * Creates a new {@link ImageTransformation} instance.
+         */
+        public ImageTransformation build() {
+            throwIfDestroyed();
+            mDestroyed = true;
+            return new ImageTransformation(this);
+        }
+
+        private void throwIfDestroyed() {
+            Preconditions.checkState(!mDestroyed, "Already called build()");
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "ImageTransformation: [id=" + mId + ", options=" + mOptions + "]";
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeParcelable(mId, flags);
+
+        final int size = mOptions.size();
+        final Pattern[] patterns = new Pattern[size];
+        final int[] resIds = new int[size];
+        final CharSequence[] contentDescriptions = new String[size];
+        for (int i = 0; i < size; i++) {
+            final Option option = mOptions.get(i);
+            patterns[i] = option.pattern;
+            resIds[i] = option.resId;
+            contentDescriptions[i] = option.contentDescription;
+        }
+        parcel.writeSerializable(patterns);
+        parcel.writeIntArray(resIds);
+        parcel.writeCharSequenceArray(contentDescriptions);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<ImageTransformation> CREATOR =
+            new Parcelable.Creator<ImageTransformation>() {
+        @Override
+        public ImageTransformation createFromParcel(Parcel parcel) {
+            final AutofillId id = parcel.readParcelable(null);
+
+            final Pattern[] regexs = (Pattern[]) parcel.readSerializable();
+            final int[] resIds = parcel.createIntArray();
+            final CharSequence[] contentDescriptions = parcel.readCharSequenceArray();
+
+            // 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 CharSequence contentDescription = contentDescriptions[0];
+            final ImageTransformation.Builder builder = (contentDescription != null)
+                    ? new ImageTransformation.Builder(id, regexs[0], resIds[0], contentDescription)
+                    : new ImageTransformation.Builder(id, regexs[0], resIds[0]);
+
+            final int size = regexs.length;
+            for (int i = 1; i < size; i++) {
+                if (contentDescriptions[i] != null) {
+                    builder.addOption(regexs[i], resIds[i], contentDescriptions[i]);
+                } else {
+                    builder.addOption(regexs[i], resIds[i]);
+                }
+            }
+
+            return builder.build();
+        }
+
+        @Override
+        public ImageTransformation[] newArray(int size) {
+            return new ImageTransformation[size];
+        }
+    };
+
+    private static final class Option {
+        public final Pattern pattern;
+        public final int resId;
+        public final CharSequence contentDescription;
+
+        Option(Pattern pattern, int resId, CharSequence contentDescription) {
+            this.pattern = pattern;
+            this.resId = resId;
+            this.contentDescription = TextUtils.trimNoCopySpans(contentDescription);
+        }
+    }
+}
diff --git a/android/service/autofill/InlinePresentation.java b/android/service/autofill/InlinePresentation.java
new file mode 100644
index 0000000..9cf1b87
--- /dev/null
+++ b/android/service/autofill/InlinePresentation.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2019 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.NonNull;
+import android.annotation.Size;
+import android.app.slice.Slice;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.widget.inline.InlinePresentationSpec;
+
+import com.android.internal.util.DataClass;
+
+import java.util.List;
+
+/**
+ * Wrapper class holding a {@link Slice} and an {@link InlinePresentationSpec} for rendering UI
+ * for an Inline Suggestion.
+ */
+@DataClass(
+        genToString = true,
+        genHiddenConstDefs = true,
+        genEqualsHashCode = true)
+public final class InlinePresentation implements Parcelable {
+
+
+    /**
+     * Represents the UI content and the action for the inline suggestion.
+     */
+    private final @NonNull Slice mSlice;
+
+    /**
+     * Specifies the UI specification for the inline suggestion.
+     */
+    private final @NonNull InlinePresentationSpec mInlinePresentationSpec;
+
+    /**
+     * Indicates whether the UI should be pinned, hence non-scrollable and non-filterable, in the
+     * host.
+     */
+    private final boolean mPinned;
+
+    /**
+     * Returns the autofill hints set in the slice.
+     *
+     * @hide
+     */
+    @NonNull
+    @Size(min = 0)
+    public String[] getAutofillHints() {
+        List<String> hints = mSlice.getHints();
+        return hints.toArray(new String[hints.size()]);
+    }
+
+
+
+    // Code below generated by codegen v1.0.15.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/InlinePresentation.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new InlinePresentation.
+     *
+     * @param slice
+     *   Represents the UI content and the action for the inline suggestion.
+     * @param inlinePresentationSpec
+     *   Specifies the UI specification for the inline suggestion.
+     * @param pinned
+     *   Indicates whether the UI should be pinned, hence non-scrollable and non-filterable, in the
+     *   host.
+     */
+    @DataClass.Generated.Member
+    public InlinePresentation(
+            @NonNull Slice slice,
+            @NonNull InlinePresentationSpec inlinePresentationSpec,
+            boolean pinned) {
+        this.mSlice = slice;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSlice);
+        this.mInlinePresentationSpec = inlinePresentationSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInlinePresentationSpec);
+        this.mPinned = pinned;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * Represents the UI content and the action for the inline suggestion.
+     */
+    @DataClass.Generated.Member
+    public @NonNull Slice getSlice() {
+        return mSlice;
+    }
+
+    /**
+     * Specifies the UI specification for the inline suggestion.
+     */
+    @DataClass.Generated.Member
+    public @NonNull InlinePresentationSpec getInlinePresentationSpec() {
+        return mInlinePresentationSpec;
+    }
+
+    /**
+     * Indicates whether the UI should be pinned, hence non-scrollable and non-filterable, in the
+     * host.
+     */
+    @DataClass.Generated.Member
+    public boolean isPinned() {
+        return mPinned;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "InlinePresentation { " +
+                "slice = " + mSlice + ", " +
+                "inlinePresentationSpec = " + mInlinePresentationSpec + ", " +
+                "pinned = " + mPinned +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(InlinePresentation other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        InlinePresentation that = (InlinePresentation) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Objects.equals(mSlice, that.mSlice)
+                && java.util.Objects.equals(mInlinePresentationSpec, that.mInlinePresentationSpec)
+                && mPinned == that.mPinned;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mSlice);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mInlinePresentationSpec);
+        _hash = 31 * _hash + Boolean.hashCode(mPinned);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mPinned) flg |= 0x4;
+        dest.writeByte(flg);
+        dest.writeTypedObject(mSlice, flags);
+        dest.writeTypedObject(mInlinePresentationSpec, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ InlinePresentation(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        boolean pinned = (flg & 0x4) != 0;
+        Slice slice = (Slice) in.readTypedObject(Slice.CREATOR);
+        InlinePresentationSpec inlinePresentationSpec = (InlinePresentationSpec) in.readTypedObject(InlinePresentationSpec.CREATOR);
+
+        this.mSlice = slice;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSlice);
+        this.mInlinePresentationSpec = inlinePresentationSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInlinePresentationSpec);
+        this.mPinned = pinned;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<InlinePresentation> CREATOR
+            = new Parcelable.Creator<InlinePresentation>() {
+        @Override
+        public InlinePresentation[] newArray(int size) {
+            return new InlinePresentation[size];
+        }
+
+        @Override
+        public InlinePresentation createFromParcel(@NonNull Parcel in) {
+            return new InlinePresentation(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1586992400667L,
+            codegenVersion = "1.0.15",
+            sourceFile = "frameworks/base/core/java/android/service/autofill/InlinePresentation.java",
+            inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final  boolean mPinned\npublic @android.annotation.NonNull @android.annotation.Size(min=0L) java.lang.String[] getAutofillHints()\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\[email protected](genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/service/autofill/InlineSuggestionRenderService.java b/android/service/autofill/InlineSuggestionRenderService.java
new file mode 100644
index 0000000..6c22b19
--- /dev/null
+++ b/android/service/autofill/InlineSuggestionRenderService.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2020 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 com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.Service;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.graphics.PixelFormat;
+import android.os.BaseBundle;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Size;
+import android.view.Display;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+/**
+ * A service that renders an inline presentation view given the {@link InlinePresentation}.
+ *
+ * {@hide}
+ */
+@SystemApi
+@TestApi
+public abstract class InlineSuggestionRenderService extends Service {
+
+    private static final String TAG = "InlineSuggestionRenderService";
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     *
+     * <p>To be supported, the service must also require the
+     * {@link android.Manifest.permission#BIND_INLINE_SUGGESTION_RENDER_SERVICE} permission so that
+     * other applications can not abuse it.
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.service.autofill.InlineSuggestionRenderService";
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
+
+    private IInlineSuggestionUiCallback mCallback;
+
+    /**
+     * If the specified {@code width}/{@code height} is an exact value, then it will be returned as
+     * is, otherwise the method tries to measure a size that is just large enough to fit the view
+     * content, within constraints posed by {@code minSize} and {@code maxSize}.
+     *
+     * @param view    the view for which we measure the size
+     * @param width   the expected width of the view, either an exact value or {@link
+     *                ViewGroup.LayoutParams#WRAP_CONTENT}
+     * @param height  the expected width of the view, either an exact value or {@link
+     *                ViewGroup.LayoutParams#WRAP_CONTENT}
+     * @param minSize the lower bound of the size to be returned
+     * @param maxSize the upper bound of the size to be returned
+     * @return the measured size of the view based on the given size constraints.
+     */
+    private Size measuredSize(@NonNull View view, int width, int height, @NonNull Size minSize,
+            @NonNull Size maxSize) {
+        if (width != ViewGroup.LayoutParams.WRAP_CONTENT
+                && height != ViewGroup.LayoutParams.WRAP_CONTENT) {
+            return new Size(width, height);
+        }
+        int widthMeasureSpec;
+        if (width == ViewGroup.LayoutParams.WRAP_CONTENT) {
+            widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxSize.getWidth(),
+                    View.MeasureSpec.AT_MOST);
+        } else {
+            widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+        }
+        int heightMeasureSpec;
+        if (height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+            heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxSize.getHeight(),
+                    View.MeasureSpec.AT_MOST);
+        } else {
+            heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
+        }
+        view.measure(widthMeasureSpec, heightMeasureSpec);
+        return new Size(Math.max(view.getMeasuredWidth(), minSize.getWidth()),
+                Math.max(view.getMeasuredHeight(), minSize.getHeight()));
+    }
+
+    private void handleRenderSuggestion(IInlineSuggestionUiCallback callback,
+            InlinePresentation presentation, int width, int height, IBinder hostInputToken,
+            int displayId) {
+        if (hostInputToken == null) {
+            try {
+                callback.onError();
+            } catch (RemoteException e) {
+                Log.w(TAG, "RemoteException calling onError()");
+            }
+            return;
+        }
+
+        // When we create the UI it should be for the IME display
+        updateDisplay(displayId);
+        try {
+            final View suggestionView = onRenderSuggestion(presentation, width, height);
+            if (suggestionView == null) {
+                Log.w(TAG, "ExtServices failed to render the inline suggestion view.");
+                try {
+                    callback.onError();
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Null suggestion view returned by renderer");
+                }
+                return;
+            }
+            mCallback = callback;
+            final Size measuredSize = measuredSize(suggestionView, width, height,
+                    presentation.getInlinePresentationSpec().getMinSize(),
+                    presentation.getInlinePresentationSpec().getMaxSize());
+            Log.v(TAG, "width=" + width + ", height=" + height + ", measuredSize=" + measuredSize);
+
+            final InlineSuggestionRoot suggestionRoot = new InlineSuggestionRoot(this, callback);
+            suggestionRoot.addView(suggestionView);
+            WindowManager.LayoutParams lp = new WindowManager.LayoutParams(measuredSize.getWidth(),
+                    measuredSize.getHeight(), WindowManager.LayoutParams.TYPE_APPLICATION, 0,
+                    PixelFormat.TRANSPARENT);
+
+            final SurfaceControlViewHost host = new SurfaceControlViewHost(this, getDisplay(),
+                    hostInputToken);
+            host.setView(suggestionRoot, lp);
+
+            // Set the suggestion view to be non-focusable so that if its background is set to a
+            // ripple drawable, the ripple won't be shown initially.
+            suggestionView.setFocusable(false);
+            suggestionView.setOnClickListener((v) -> {
+                try {
+                    callback.onClick();
+                } catch (RemoteException e) {
+                    Log.w(TAG, "RemoteException calling onClick()");
+                }
+            });
+            final View.OnLongClickListener onLongClickListener =
+                    suggestionView.getOnLongClickListener();
+            suggestionView.setOnLongClickListener((v) -> {
+                if (onLongClickListener != null) {
+                    onLongClickListener.onLongClick(v);
+                }
+                try {
+                    callback.onLongClick();
+                } catch (RemoteException e) {
+                    Log.w(TAG, "RemoteException calling onLongClick()");
+                }
+                return true;
+            });
+
+            sendResult(callback, host.getSurfacePackage(), measuredSize.getWidth(),
+                    measuredSize.getHeight());
+        } finally {
+            updateDisplay(Display.DEFAULT_DISPLAY);
+        }
+    }
+
+    private void handleGetInlineSuggestionsRendererInfo(@NonNull RemoteCallback callback) {
+        final Bundle rendererInfo = onGetInlineSuggestionsRendererInfo();
+        callback.sendResult(rendererInfo);
+    }
+
+    private void sendResult(@NonNull IInlineSuggestionUiCallback callback,
+            @Nullable SurfaceControlViewHost.SurfacePackage surface, int width, int height) {
+        try {
+            callback.onContent(surface, width, height);
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException calling onContent(" + surface + ")");
+        }
+    }
+
+    @Override
+    @Nullable
+    public final IBinder onBind(@NonNull Intent intent) {
+        BaseBundle.setShouldDefuse(true);
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return new IInlineSuggestionRenderService.Stub() {
+                @Override
+                public void renderSuggestion(@NonNull IInlineSuggestionUiCallback callback,
+                        @NonNull InlinePresentation presentation, int width, int height,
+                        @Nullable IBinder hostInputToken, int displayId) {
+                    mHandler.sendMessage(
+                            obtainMessage(InlineSuggestionRenderService::handleRenderSuggestion,
+                                    InlineSuggestionRenderService.this, callback, presentation,
+                                    width, height, hostInputToken, displayId));
+                }
+
+                @Override
+                public void getInlineSuggestionsRendererInfo(@NonNull RemoteCallback callback) {
+                    mHandler.sendMessage(obtainMessage(
+                            InlineSuggestionRenderService::handleGetInlineSuggestionsRendererInfo,
+                            InlineSuggestionRenderService.this, callback));
+                }
+            }.asBinder();
+        }
+
+        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+        return null;
+    }
+
+    /**
+     * Starts the {@link IntentSender} from the client app.
+     *
+     * @param intentSender the {@link IntentSender} to start the attribution UI from the client
+     *                     app.
+     */
+    public final void startIntentSender(@NonNull IntentSender intentSender) {
+        if (mCallback == null) return;
+        try {
+            mCallback.onStartIntentSender(intentSender);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the metadata about the renderer. Returns {@code Bundle.Empty} if no metadata is
+     * provided.
+     */
+    @NonNull
+    public Bundle onGetInlineSuggestionsRendererInfo() {
+        return Bundle.EMPTY;
+    }
+
+    /**
+     * Renders the slice into a view.
+     */
+    @Nullable
+    public View onRenderSuggestion(@NonNull InlinePresentation presentation, int width,
+            int height) {
+        Log.e(TAG, "service implementation (" + getClass() + " does not implement "
+                + "onRenderSuggestion()");
+        return null;
+    }
+}
diff --git a/android/service/autofill/InlineSuggestionRoot.java b/android/service/autofill/InlineSuggestionRoot.java
new file mode 100644
index 0000000..c879653
--- /dev/null
+++ b/android/service/autofill/InlineSuggestionRoot.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2020 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.NonNull;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.widget.FrameLayout;
+
+/**
+ * This class is the root view for an inline suggestion. It is responsible for
+ * detecting the click on the item and to also transfer input focus to the IME
+ * window if we detect the user is scrolling.
+ *
+ * @hide
+ */
+@SuppressLint("ViewConstructor")
+public class InlineSuggestionRoot extends FrameLayout {
+    private static final String TAG = "InlineSuggestionRoot";
+
+    private final @NonNull IInlineSuggestionUiCallback mCallback;
+    private final int mTouchSlop;
+
+    private float mDownX;
+    private float mDownY;
+
+    public InlineSuggestionRoot(@NonNull Context context,
+            @NonNull IInlineSuggestionUiCallback callback) {
+        super(context);
+        mCallback = callback;
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        setFocusable(false);
+    }
+
+    @Override
+    @SuppressLint("ClickableViewAccessibility")
+    public boolean dispatchTouchEvent(@NonNull MotionEvent event) {
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN: {
+                mDownX = event.getX();
+                mDownY = event.getY();
+            } break;
+
+            case MotionEvent.ACTION_MOVE: {
+                final float distance = MathUtils.dist(mDownX, mDownY,
+                        event.getX(), event.getY());
+                final boolean isSecure = (event.getFlags()
+                        & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) == 0;
+                if (!isSecure || distance > mTouchSlop) {
+                    try {
+                        mCallback.onTransferTouchFocusToImeWindow(getViewRootImpl().getInputToken(),
+                                getContext().getDisplayId());
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "RemoteException transferring touch focus to IME");
+                    }
+                }
+            } break;
+        }
+        return super.dispatchTouchEvent(event);
+    }
+}
diff --git a/android/service/autofill/InternalOnClickAction.java b/android/service/autofill/InternalOnClickAction.java
new file mode 100644
index 0000000..6602f2d
--- /dev/null
+++ b/android/service/autofill/InternalOnClickAction.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcelable;
+import android.view.ViewGroup;
+
+/**
+ * Superclass of all {@link OnClickAction} the system understands. As this is not public, all public
+ * subclasses have to implement {@link OnClickAction} again.
+ *
+ * @hide
+ */
+@TestApi
+public abstract class InternalOnClickAction implements OnClickAction, Parcelable {
+
+    /**
+     * Applies the action to the children of the {@code rootView} when clicked.
+     */
+    public abstract void onClick(@NonNull ViewGroup rootView);
+}
diff --git a/android/service/autofill/InternalSanitizer.java b/android/service/autofill/InternalSanitizer.java
new file mode 100644
index 0000000..ccffc70
--- /dev/null
+++ b/android/service/autofill/InternalSanitizer.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcelable;
+import android.view.autofill.AutofillValue;
+
+/**
+ * Superclass of all sanitizers the system understands. As this is not public all public subclasses
+ * have to implement {@link Sanitizer} again.
+ *
+ * @hide
+ */
+@TestApi
+public abstract class InternalSanitizer implements Sanitizer, Parcelable {
+
+    /**
+     * Sanitizes an {@link AutofillValue}.
+     *
+     * @return sanitized value or {@code null} if value could not be sanitized (for example: didn't
+     * match regex, it's an invalid type, regex failed, etc).
+     */
+    @Nullable
+    public abstract AutofillValue sanitize(@NonNull AutofillValue value);
+}
diff --git a/android/service/autofill/InternalTransformation.java b/android/service/autofill/InternalTransformation.java
new file mode 100644
index 0000000..0dba2b9
--- /dev/null
+++ b/android/service/autofill/InternalTransformation.java
@@ -0,0 +1,81 @@
+/*
+ * 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;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.Pair;
+import android.widget.RemoteViews;
+
+import java.util.ArrayList;
+
+/**
+ * Superclass of all transformation the system understands. As this is not public all
+ * subclasses have to implement {@link Transformation} again.
+ *
+ * @hide
+ */
+@TestApi
+public abstract class InternalTransformation implements Transformation, Parcelable {
+
+    private static final String TAG = "InternalTransformation";
+
+    /**
+     * Applies this transformation to a child view of a {@link android.widget.RemoteViews
+     * presentation template}.
+     *
+     * @param finder object used to find the value of a field in the screen.
+     * @param template the {@link RemoteViews presentation template}.
+     * @param childViewId resource id of the child view inside the template.
+     */
+    abstract void apply(@NonNull ValueFinder finder, @NonNull RemoteViews template,
+            int childViewId) throws Exception;
+
+    /**
+     * Applies multiple transformations to the children views of a
+     * {@link android.widget.RemoteViews presentation template}.
+     *
+     * @param finder object used to find the value of a field in the screen.
+     * @param template the {@link RemoteViews presentation template}.
+     * @param transformations map of resource id of the child view inside the template to
+     * transformation.
+     */
+    public static boolean batchApply(@NonNull ValueFinder finder, @NonNull RemoteViews template,
+            @NonNull ArrayList<Pair<Integer, InternalTransformation>> transformations) {
+        final int size = transformations.size();
+        if (sDebug) Log.d(TAG, "getPresentation(): applying " + size + " transformations");
+        for (int i = 0; i < size; i++) {
+            final Pair<Integer, InternalTransformation> pair = transformations.get(i);
+            final int id = pair.first;
+            final InternalTransformation transformation = pair.second;
+            if (sDebug) Log.d(TAG, "#" + i + ": " + transformation);
+
+            try {
+                transformation.apply(finder, template, id);
+            } catch (Exception e) {
+                // Do not log full exception to avoid PII leaking
+                Log.e(TAG, "Could not apply transformation " + transformation + ": "
+                        + e.getClass());
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/android/service/autofill/InternalValidator.java b/android/service/autofill/InternalValidator.java
new file mode 100644
index 0000000..4bea98d
--- /dev/null
+++ b/android/service/autofill/InternalValidator.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcelable;
+
+/**
+ * Superclass of all validators the system understands. As this is not public all public subclasses
+ * have to implement {@link Validator} again.
+ *
+ * @hide
+ */
+@TestApi
+public abstract class InternalValidator implements Validator, Parcelable {
+
+    /**
+     * Decides whether the contents of the screen are valid.
+     *
+     * @param finder object used to find the value of a field in the screen.
+     * @return {@code true} if the contents are valid, {@code false} otherwise.
+     */
+    public abstract boolean isValid(@NonNull ValueFinder finder);
+}
diff --git a/android/service/autofill/LuhnChecksumValidator.java b/android/service/autofill/LuhnChecksumValidator.java
new file mode 100644
index 0000000..ef0bd74
--- /dev/null
+++ b/android/service/autofill/LuhnChecksumValidator.java
@@ -0,0 +1,139 @@
+/*
+ * 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;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+
+/**
+ * Validator that returns {@code true} if the number created by concatenating all given fields
+ * pass a Luhn algorithm checksum. All non-digits are ignored.
+ *
+ * <p>See {@link SaveInfo.Builder#setValidator(Validator)} for examples.
+ */
+public final class LuhnChecksumValidator extends InternalValidator implements Validator,
+        Parcelable {
+    private static final String TAG = "LuhnChecksumValidator";
+
+    private final AutofillId[] mIds;
+
+    /**
+      * Default constructor.
+      *
+      * @param ids id of fields that comprises the number to be checked.
+      */
+    public LuhnChecksumValidator(@NonNull AutofillId... ids) {
+        mIds = Preconditions.checkArrayElementsNotNull(ids, "ids");
+    }
+
+    /**
+     * Checks if the Luhn checksum is valid.
+     *
+     * @param number The number including the checksum
+     */
+    private static boolean isLuhnChecksumValid(@NonNull String number) {
+        int sum = 0;
+        boolean isDoubled = false;
+
+        for (int i = number.length() - 1; i >= 0; i--) {
+            final int digit = number.charAt(i) - '0';
+            if (digit < 0 || digit > 9) {
+                // Ignore non-digits
+                continue;
+            }
+
+            int addend;
+            if (isDoubled) {
+                addend = digit * 2;
+                if (addend > 9) {
+                    addend -= 9;
+                }
+            } else {
+                addend = digit;
+            }
+            sum += addend;
+            isDoubled = !isDoubled;
+        }
+
+        return sum % 10 == 0;
+    }
+
+    /** @hide */
+    @Override
+    @TestApi
+    public boolean isValid(@NonNull ValueFinder finder) {
+        if (mIds == null || mIds.length == 0) return false;
+
+        final StringBuilder builder = new StringBuilder();
+        for (AutofillId id : mIds) {
+            final String partialNumber = finder.findByAutofillId(id);
+            if (partialNumber == null) {
+                if (sDebug) Log.d(TAG, "No partial number for id " + id);
+                return false;
+            }
+            builder.append(partialNumber);
+        }
+
+        final String number = builder.toString();
+        boolean valid = isLuhnChecksumValid(number);
+        if (sDebug) Log.d(TAG, "isValid(" + number.length() + " chars): " + valid);
+        return valid;
+    }
+
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "LuhnChecksumValidator: [ids=" + Arrays.toString(mIds) + "]";
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeParcelableArray(mIds, flags);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<LuhnChecksumValidator> CREATOR =
+            new Parcelable.Creator<LuhnChecksumValidator>() {
+        @Override
+        public LuhnChecksumValidator createFromParcel(Parcel parcel) {
+            return new LuhnChecksumValidator(parcel.readParcelableArray(null, AutofillId.class));
+        }
+
+        @Override
+        public LuhnChecksumValidator[] newArray(int size) {
+            return new LuhnChecksumValidator[size];
+        }
+    };
+}
diff --git a/android/service/autofill/NegationValidator.java b/android/service/autofill/NegationValidator.java
new file mode 100644
index 0000000..2f098e2
--- /dev/null
+++ b/android/service/autofill/NegationValidator.java
@@ -0,0 +1,79 @@
+/*
+ * 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;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Validator used to implement a {@code NOT} logical operation.
+ *
+ * @hide
+ */
+final class NegationValidator extends InternalValidator {
+    @NonNull private final InternalValidator mValidator;
+
+    NegationValidator(@NonNull InternalValidator validator) {
+        mValidator = Preconditions.checkNotNull(validator);
+    }
+
+    @Override
+    public boolean isValid(@NonNull ValueFinder finder) {
+        return !mValidator.isValid(finder);
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "NegationValidator: [validator=" + mValidator + "]";
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mValidator, flags);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<NegationValidator> CREATOR =
+            new Parcelable.Creator<NegationValidator>() {
+        @Override
+        public NegationValidator createFromParcel(Parcel parcel) {
+            return new NegationValidator(parcel.readParcelable(null));
+        }
+
+        @Override
+        public NegationValidator[] newArray(int size) {
+            return new NegationValidator[size];
+        }
+    };
+}
diff --git a/android/service/autofill/OnClickAction.java b/android/service/autofill/OnClickAction.java
new file mode 100644
index 0000000..8597a88
--- /dev/null
+++ b/android/service/autofill/OnClickAction.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill;
+
+/**
+ * Class used to define an action to be performed when a child view in a
+ * {@link android.widget.RemoteViews presentation} is clicked.
+ *
+ * <p>Typically used to switch the visibility of other views in a
+ * {@link CustomDescription custom save UI}.
+ *
+ * <p><b>Note:</b> This interface is not meant to be implemented by app developers; only
+ * implementations provided by the Android System can be used in other Autofill APIs.
+ */
+public interface OnClickAction {
+}
diff --git a/android/service/autofill/OptionalValidators.java b/android/service/autofill/OptionalValidators.java
new file mode 100644
index 0000000..7189c88
--- /dev/null
+++ b/android/service/autofill/OptionalValidators.java
@@ -0,0 +1,95 @@
+/*
+ * 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;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Compound validator that returns {@code true} on {@link #isValid(ValueFinder)} if any
+ * of its subvalidators returns {@code true} as well.
+ *
+ * <p>Used to implement an {@code OR} logical operation.
+ *
+ * @hide
+ */
+final class OptionalValidators extends InternalValidator {
+
+    private static final String TAG = "OptionalValidators";
+
+    @NonNull private final InternalValidator[] mValidators;
+
+    OptionalValidators(@NonNull InternalValidator[] validators) {
+        mValidators = Preconditions.checkArrayElementsNotNull(validators, "validators");
+    }
+
+    @Override
+    public boolean isValid(@NonNull ValueFinder finder) {
+        for (InternalValidator validator : mValidators) {
+            final boolean valid = validator.isValid(finder);
+            if (sDebug) Log.d(TAG, "isValid(" + validator + "): " + valid);
+            if (valid) return true;
+        }
+
+        return false;
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return new StringBuilder("OptionalValidators: [validators=").append(mValidators)
+                .append("]")
+                .toString();
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelableArray(mValidators, flags);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<OptionalValidators> CREATOR =
+            new Parcelable.Creator<OptionalValidators>() {
+        @Override
+        public OptionalValidators createFromParcel(Parcel parcel) {
+            return new OptionalValidators(parcel
+                .readParcelableArray(null, InternalValidator.class));
+        }
+
+        @Override
+        public OptionalValidators[] newArray(int size) {
+            return new OptionalValidators[size];
+        }
+    };
+}
diff --git a/android/service/autofill/RegexValidator.java b/android/service/autofill/RegexValidator.java
new file mode 100644
index 0000000..8cb67d0
--- /dev/null
+++ b/android/service/autofill/RegexValidator.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.regex.Pattern;
+
+/**
+ * Defines if a field is valid based on a regular expression (regex).
+ *
+ * <p>See {@link SaveInfo.Builder#setValidator(Validator)} for examples.
+ */
+public final class RegexValidator extends InternalValidator implements Validator, Parcelable {
+
+    private static final String TAG = "RegexValidator";
+
+    private final AutofillId mId;
+    private final Pattern mRegex;
+
+    /**
+     * Default constructor.
+     *
+     * @param id id of the field whose regex is applied to.
+     * @param regex regular expression that defines the result of the validator: if the regex
+     * matches the contents of the field identified by {@code id}, it returns {@code true};
+     * otherwise, it returns {@code false}.
+      */
+    public RegexValidator(@NonNull AutofillId id, @NonNull Pattern regex) {
+        mId = Preconditions.checkNotNull(id);
+        mRegex = Preconditions.checkNotNull(regex);
+    }
+
+    /** @hide */
+    @Override
+    @TestApi
+    public boolean isValid(@NonNull ValueFinder finder) {
+        final String value = finder.findByAutofillId(mId);
+        if (value == null) {
+            Log.w(TAG, "No view for id " + mId);
+            return false;
+        }
+
+        final boolean valid = mRegex.matcher(value).matches();
+        if (sDebug) Log.d(TAG, "isValid(): " + valid);
+        return valid;
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "RegexValidator: [id=" + mId + ", regex=" + mRegex + "]";
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeParcelable(mId, flags);
+        parcel.writeSerializable(mRegex);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<RegexValidator> CREATOR =
+            new Parcelable.Creator<RegexValidator>() {
+        @Override
+        public RegexValidator createFromParcel(Parcel parcel) {
+            return new RegexValidator(parcel.readParcelable(null),
+                    (Pattern) parcel.readSerializable());
+        }
+
+        @Override
+        public RegexValidator[] newArray(int size) {
+            return new RegexValidator[size];
+        }
+    };
+}
diff --git a/android/service/autofill/RequiredValidators.java b/android/service/autofill/RequiredValidators.java
new file mode 100644
index 0000000..619eba0
--- /dev/null
+++ b/android/service/autofill/RequiredValidators.java
@@ -0,0 +1,94 @@
+/*
+ * 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;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Compound validator that only returns {@code true} on {@link #isValid(ValueFinder)} if all
+ * of its subvalidators return {@code true} as well.
+ *
+ * <p>Used to implement an {@code AND} logical operation.
+ *
+ * @hide
+ */
+final class RequiredValidators extends InternalValidator {
+
+    private static final String TAG = "RequiredValidators";
+
+    @NonNull private final InternalValidator[] mValidators;
+
+    RequiredValidators(@NonNull InternalValidator[] validators) {
+        mValidators = Preconditions.checkArrayElementsNotNull(validators, "validators");
+    }
+
+    @Override
+    public boolean isValid(@NonNull ValueFinder finder) {
+        for (InternalValidator validator : mValidators) {
+            final boolean valid = validator.isValid(finder);
+            if (sDebug) Log.d(TAG, "isValid(" + validator + "): " + valid);
+            if (!valid) return false;
+        }
+        return true;
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return new StringBuilder("RequiredValidators: [validators=").append(mValidators)
+                .append("]")
+                .toString();
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelableArray(mValidators, flags);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<RequiredValidators> CREATOR =
+            new Parcelable.Creator<RequiredValidators>() {
+        @Override
+        public RequiredValidators createFromParcel(Parcel parcel) {
+            return new RequiredValidators(parcel
+                .readParcelableArray(null, InternalValidator.class));
+        }
+
+        @Override
+        public RequiredValidators[] newArray(int size) {
+            return new RequiredValidators[size];
+        }
+    };
+}
diff --git a/android/service/autofill/Sanitizer.java b/android/service/autofill/Sanitizer.java
new file mode 100644
index 0000000..8a1310d
--- /dev/null
+++ b/android/service/autofill/Sanitizer.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * Helper class used to sanitize user input before using it in a save request.
+ *
+ * <p>Typically used to avoid displaying the save UI for values that are autofilled but reformatted
+ * by the app&mdash;for example, if the autofill service sends a credit card number
+ * value as "004815162342108" and the app automatically changes it to "0048 1516 2342 108".
+ *
+ * <p><b>Note:</b> This interface is not meant to be implemented by app developers; only
+ * implementations provided by the Android System can be used in other Autofill APIs.
+ */
+public interface Sanitizer {
+}
diff --git a/android/service/autofill/SaveCallback.java b/android/service/autofill/SaveCallback.java
new file mode 100644
index 0000000..1753ecf
--- /dev/null
+++ b/android/service/autofill/SaveCallback.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 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.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.IntentSender;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Handles save requests from the {@link AutofillService} into the {@link Activity} being
+ * autofilled.
+ */
+public final class SaveCallback {
+
+    private static final String TAG = "SaveCallback";
+
+    private final ISaveCallback mCallback;
+    private boolean mCalled;
+
+    /** @hide */
+    SaveCallback(ISaveCallback callback) {
+        mCallback = callback;
+    }
+
+    /**
+     * Notifies the Android System that an
+     * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully handled
+     * by the service.
+     *
+     * @throws IllegalStateException if this method, {@link #onSuccess(IntentSender)}, or
+     * {@link #onFailure(CharSequence)} was already called.
+     */
+    public void onSuccess() {
+        onSuccessInternal(null);
+    }
+
+    /**
+     * Notifies the Android System that an
+     * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully handled
+     * by the service.
+     *
+     * <p>This method is useful when the service requires extra work&mdash;for example, launching an
+     * activity asking the user to authenticate first &mdash;before it can process the request,
+     * as the intent will be launched from the context of the activity being autofilled and hence
+     * will be part of that activity's stack.
+     *
+     * @param intentSender intent that will be launched from the context of activity being
+     * autofilled.
+     *
+     * @throws IllegalStateException if this method, {@link #onSuccess()},
+     * or {@link #onFailure(CharSequence)} was already called.
+     */
+    public void onSuccess(@NonNull IntentSender intentSender) {
+        onSuccessInternal(Preconditions.checkNotNull(intentSender));
+    }
+
+    private void onSuccessInternal(@Nullable IntentSender intentSender) {
+        assertNotCalled();
+        mCalled = true;
+        try {
+            mCallback.onSuccess(intentSender);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+
+
+
+    /**
+     * Notifies the Android System that an
+     * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} could not be handled
+     * by the service.
+     *
+     * <p>This method is just used for logging purposes, the Android System won't call the service
+     * again in case of failures&mdash;if you need to recover from the failure, just save the
+     * {@link SaveRequest} and try again later.
+     *
+     * <p><b>Note: </b>for apps targeting {@link android.os.Build.VERSION_CODES#Q} or higher, this
+     * method just logs the message on {@code logcat}; for apps targetting older SDKs, it also
+     * displays the message to user using a {@link android.widget.Toast}.
+     *
+     * @param message error message. <b>Note: </b> this message should <b>not</b> contain PII
+     * (Personally Identifiable Information, such as username or email address).
+     *
+     * @throws IllegalStateException if this method, {@link #onSuccess()},
+     * or {@link #onSuccess(IntentSender)} was already called.
+     */
+    public void onFailure(CharSequence message) {
+        Log.w(TAG, "onFailure(): " + message);
+        assertNotCalled();
+        mCalled = true;
+        try {
+            mCallback.onFailure(message);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    private void assertNotCalled() {
+        if (mCalled) {
+            throw new IllegalStateException("Already called");
+        }
+    }
+}
diff --git a/android/service/autofill/SaveInfo.java b/android/service/autofill/SaveInfo.java
new file mode 100644
index 0000000..e640eec
--- /dev/null
+++ b/android/service/autofill/SaveInfo.java
@@ -0,0 +1,924 @@
+/*
+ * 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;
+
+import static android.service.autofill.AutofillServiceHelper.assertValid;
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.IntentSender;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.DebugUtils;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * Information used to indicate that an {@link AutofillService} is interested on saving the
+ * user-inputed data for future use, through a
+ * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
+ * call.
+ *
+ * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}, and it contains at least
+ * two pieces of information:
+ *
+ * <ol>
+ *   <li>The type(s) of user data (like password or credit card info) that would be saved.
+ *   <li>The minimum set of views (represented by their {@link AutofillId}) that need to be changed
+ *       to trigger a save request.
+ * </ol>
+ *
+ * <p>Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}:
+ *
+ * <pre class="prettyprint">
+ *   new FillResponse.Builder()
+ *       .addDataset(new Dataset.Builder()
+ *           .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer")) // username
+ *           .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer")) // password
+ *           .build())
+ *       .setSaveInfo(new SaveInfo.Builder(
+ *           SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ *           new AutofillId[] { id1, id2 }).build())
+ *       .build();
+ * </pre>
+ *
+ * <p>The save type flags are used to display the appropriate strings in the autofill save UI.
+ * You can pass multiple values, but try to keep it short if possible. In the above example, just
+ * {@code SaveInfo.SAVE_DATA_TYPE_PASSWORD} would be enough.
+ *
+ * <p>There might be cases where the {@link AutofillService} knows how to fill the screen,
+ * but the user has no data for it. In that case, the {@link FillResponse} should contain just the
+ * {@link SaveInfo}, but no {@link Dataset Datasets}:
+ *
+ * <pre class="prettyprint">
+ *   new FillResponse.Builder()
+ *       .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ *           new AutofillId[] { id1, id2 }).build())
+ *       .build();
+ * </pre>
+ *
+ * <p>There might be cases where the user data in the {@link AutofillService} is enough
+ * to populate some fields but not all, and the service would still be interested on saving the
+ * other fields. In that case, the service could set the
+ * {@link SaveInfo.Builder#setOptionalIds(AutofillId[])} as well:
+ *
+ * <pre class="prettyprint">
+ *   new FillResponse.Builder()
+ *       .addDataset(new Dataset.Builder()
+ *           .setValue(id1, AutofillValue.forText("742 Evergreen Terrace"),
+ *               createPresentation("742 Evergreen Terrace")) // street
+ *           .setValue(id2, AutofillValue.forText("Springfield"),
+ *               createPresentation("Springfield")) // city
+ *           .build())
+ *       .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_ADDRESS,
+ *           new AutofillId[] { id1, id2 }) // street and  city
+ *           .setOptionalIds(new AutofillId[] { id3, id4 }) // state and zipcode
+ *           .build())
+ *       .build();
+ * </pre>
+ *
+ * <a name="TriggeringSaveRequest"></a>
+ * <h3>Triggering a save request</h3>
+ *
+ * <p>The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after
+ * any of the following events:
+ * <ul>
+ *   <li>The {@link Activity} finishes.
+ *   <li>The app explicitly calls {@link AutofillManager#commit()}.
+ *   <li>All required views become invisible (if the {@link SaveInfo} was created with the
+ *       {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag).
+ *   <li>The user clicks a specific view (defined by {@link Builder#setTriggerId(AutofillId)}.
+ * </ul>
+ *
+ * <p>But it is only triggered when all conditions below are met:
+ * <ul>
+ *   <li>The {@link SaveInfo} associated with the {@link FillResponse} is not {@code null} neither
+ *       has the {@link #FLAG_DELAY_SAVE} flag.
+ *   <li>The {@link AutofillValue}s of all required views (as set by the {@code requiredIds} passed
+ *       to the {@link SaveInfo.Builder} constructor are not empty.
+ *   <li>The {@link AutofillValue} of at least one view (be it required or optional) has changed
+ *       (i.e., it's neither the same value passed in a {@link Dataset}, nor the initial value
+ *       presented in the view).
+ *   <li>There is no {@link Dataset} in the last {@link FillResponse} that completely matches the
+ *       screen state (i.e., all required and optional fields in the dataset have the same value as
+ *       the fields in the screen).
+ *   <li>The user explicitly tapped the autofill save UI asking to save data for autofill.
+ * </ul>
+ *
+ * <a name="CustomizingSaveUI"></a>
+ * <h3>Customizing the autofill save UI</h3>
+ *
+ * <p>The service can also customize some aspects of the autofill save UI:
+ * <ul>
+ *   <li>Add a simple subtitle by calling {@link Builder#setDescription(CharSequence)}.
+ *   <li>Add a customized subtitle by calling
+ *       {@link Builder#setCustomDescription(CustomDescription)}.
+ *   <li>Customize the button used to reject the save request by calling
+ *       {@link Builder#setNegativeAction(int, IntentSender)}.
+ *   <li>Decide whether the UI should be shown based on the user input validation by calling
+ *       {@link Builder#setValidator(Validator)}.
+ * </ul>
+ */
+public final class SaveInfo implements Parcelable {
+
+    /**
+     * Type used when the service can save the contents of a screen, but cannot describe what
+     * the content is for.
+     */
+    public static final int SAVE_DATA_TYPE_GENERIC = 0x0;
+
+    /**
+     * Type used when the {@link FillResponse} represents user credentials that have a password.
+     */
+    public static final int SAVE_DATA_TYPE_PASSWORD = 0x01;
+
+    /**
+     * Type used on when the {@link FillResponse} represents a physical address (such as street,
+     * city, state, etc).
+     */
+    public static final int SAVE_DATA_TYPE_ADDRESS = 0x02;
+
+    /**
+     * Type used when the {@link FillResponse} represents a credit card.
+     */
+    public static final int SAVE_DATA_TYPE_CREDIT_CARD = 0x04;
+
+    /**
+     * Type used when the {@link FillResponse} represents just an username, without a password.
+     */
+    public static final int SAVE_DATA_TYPE_USERNAME = 0x08;
+
+    /**
+     * Type used when the {@link FillResponse} represents just an email address, without a password.
+     */
+    public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 0x10;
+
+    /**
+     * Type used when the {@link FillResponse} represents a debit card.
+     */
+    public static final int SAVE_DATA_TYPE_DEBIT_CARD = 0x20;
+
+    /**
+     * Type used when the {@link FillResponse} represents a payment card except for credit and
+     * debit cards.
+     */
+    public static final int SAVE_DATA_TYPE_PAYMENT_CARD = 0x40;
+
+    /**
+     * Type used when the {@link FillResponse} represents a card that does not a specified card or
+     * cannot identify what the card is for.
+     */
+    public static final int SAVE_DATA_TYPE_GENERIC_CARD = 0x80;
+
+    /**
+     * Style for the negative button of the save UI to cancel the
+     * save operation. In this case, the user tapping the negative
+     * button signals that they would prefer to not save the filled
+     * content.
+     */
+    public static final int NEGATIVE_BUTTON_STYLE_CANCEL = 0;
+
+    /**
+     * Style for the negative button of the save UI to reject the
+     * save operation. This could be useful if the user needs to
+     * opt-in your service and the save prompt is an advertisement
+     * of the potential value you can add to the user. In this
+     * case, the user tapping the negative button sends a strong
+     * signal that the feature may not be useful and you may
+     * consider some backoff strategy.
+     */
+    public static final int NEGATIVE_BUTTON_STYLE_REJECT = 1;
+
+    /**
+     * Style for the negative button of the save UI to never do the
+     * save operation. This means that the user does not need to save
+     * any data on this activity or application. Once the user tapping
+     * the negative button, the service should never trigger the save
+     * UI again. In addition to this, must consider providing restore
+     * options for the user.
+     */
+    public static final int NEGATIVE_BUTTON_STYLE_NEVER = 2;
+
+    /** @hide */
+    @IntDef(prefix = { "NEGATIVE_BUTTON_STYLE_" }, value = {
+            NEGATIVE_BUTTON_STYLE_CANCEL,
+            NEGATIVE_BUTTON_STYLE_REJECT,
+            NEGATIVE_BUTTON_STYLE_NEVER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface NegativeButtonStyle{}
+
+    /**
+     * Style for the positive button of save UI to request the save operation.
+     * In this case, the user tapping the positive button signals that they
+     * agrees to save the filled content.
+     */
+    public static final int POSITIVE_BUTTON_STYLE_SAVE = 0;
+
+    /**
+     * Style for the positive button of save UI to have next action before the save operation.
+     * This could be useful if the filled content contains sensitive personally identifiable
+     * information and then requires user confirmation or verification. In this case, the user
+     * tapping the positive button signals that they would complete the next required action
+     * to save the filled content.
+     */
+    public static final int POSITIVE_BUTTON_STYLE_CONTINUE = 1;
+
+    /** @hide */
+    @IntDef(prefix = { "POSITIVE_BUTTON_STYLE_" }, value = {
+            POSITIVE_BUTTON_STYLE_SAVE,
+            POSITIVE_BUTTON_STYLE_CONTINUE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface PositiveButtonStyle{}
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "SAVE_DATA_TYPE_" }, value = {
+            SAVE_DATA_TYPE_GENERIC,
+            SAVE_DATA_TYPE_PASSWORD,
+            SAVE_DATA_TYPE_ADDRESS,
+            SAVE_DATA_TYPE_CREDIT_CARD,
+            SAVE_DATA_TYPE_USERNAME,
+            SAVE_DATA_TYPE_EMAIL_ADDRESS,
+            SAVE_DATA_TYPE_DEBIT_CARD,
+            SAVE_DATA_TYPE_PAYMENT_CARD,
+            SAVE_DATA_TYPE_GENERIC_CARD
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface SaveDataType{}
+
+    /**
+     * Usually, a save request is only automatically <a href="#TriggeringSaveRequest">triggered</a>
+     * once the {@link Activity} finishes. If this flag is set, it is triggered once all saved views
+     * become invisible.
+     */
+    public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1;
+
+    /**
+     * By default, a save request is automatically <a href="#TriggeringSaveRequest">triggered</a>
+     * once the {@link Activity} finishes. If this flag is set, finishing the activity doesn't
+     * trigger a save request.
+     *
+     * <p>This flag is typically used in conjunction with {@link Builder#setTriggerId(AutofillId)}.
+     */
+    public static final int FLAG_DONT_SAVE_ON_FINISH = 0x2;
+
+
+    /**
+     * Postpone the autofill save UI.
+     *
+     * <p>If flag is set, the autofill save UI is not triggered when the
+     * autofill context associated with the response associated with this {@link SaveInfo} is
+     * committed (with {@link AutofillManager#commit()}). Instead, the {@link FillContext}
+     * is delivered in future fill requests (with {@link
+     * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)})
+     * and save request (with {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)})
+     * of an activity belonging to the same task.
+     *
+     * <p>This flag should be used when the service detects that the application uses
+     * multiple screens to implement an autofillable workflow (for example, one screen for the
+     * username field, another for password).
+     */
+    // TODO(b/113281366): improve documentation: add example, document relationship with other
+    // flagss, etc...
+    public static final int FLAG_DELAY_SAVE = 0x4;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+            FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE,
+            FLAG_DONT_SAVE_ON_FINISH,
+            FLAG_DELAY_SAVE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface SaveInfoFlags{}
+
+    private final @SaveDataType int mType;
+    private final @NegativeButtonStyle int mNegativeButtonStyle;
+    private final @PositiveButtonStyle int mPositiveButtonStyle;
+    private final IntentSender mNegativeActionListener;
+    private final AutofillId[] mRequiredIds;
+    private final AutofillId[] mOptionalIds;
+    private final CharSequence mDescription;
+    private final int mFlags;
+    private final CustomDescription mCustomDescription;
+    private final InternalValidator mValidator;
+    private final InternalSanitizer[] mSanitizerKeys;
+    private final AutofillId[][] mSanitizerValues;
+    private final AutofillId mTriggerId;
+
+    private SaveInfo(Builder builder) {
+        mType = builder.mType;
+        mNegativeButtonStyle = builder.mNegativeButtonStyle;
+        mNegativeActionListener = builder.mNegativeActionListener;
+        mPositiveButtonStyle = builder.mPositiveButtonStyle;
+        mRequiredIds = builder.mRequiredIds;
+        mOptionalIds = builder.mOptionalIds;
+        mDescription = builder.mDescription;
+        mFlags = builder.mFlags;
+        mCustomDescription = builder.mCustomDescription;
+        mValidator = builder.mValidator;
+        if (builder.mSanitizers == null) {
+            mSanitizerKeys = null;
+            mSanitizerValues = null;
+        } else {
+            final int size = builder.mSanitizers.size();
+            mSanitizerKeys = new InternalSanitizer[size];
+            mSanitizerValues = new AutofillId[size][];
+            for (int i = 0; i < size; i++) {
+                mSanitizerKeys[i] = builder.mSanitizers.keyAt(i);
+                mSanitizerValues[i] = builder.mSanitizers.valueAt(i);
+            }
+        }
+        mTriggerId = builder.mTriggerId;
+    }
+
+    /** @hide */
+    public @NegativeButtonStyle int getNegativeActionStyle() {
+        return mNegativeButtonStyle;
+    }
+
+    /** @hide */
+    public @Nullable IntentSender getNegativeActionListener() {
+        return mNegativeActionListener;
+    }
+
+    /** @hide */
+    public @PositiveButtonStyle int getPositiveActionStyle() {
+        return mPositiveButtonStyle;
+    }
+
+    /** @hide */
+    public @Nullable AutofillId[] getRequiredIds() {
+        return mRequiredIds;
+    }
+
+    /** @hide */
+    public @Nullable AutofillId[] getOptionalIds() {
+        return mOptionalIds;
+    }
+
+    /** @hide */
+    public @SaveDataType int getType() {
+        return mType;
+    }
+
+    /** @hide */
+    public @SaveInfoFlags int getFlags() {
+        return mFlags;
+    }
+
+    /** @hide */
+    public CharSequence getDescription() {
+        return mDescription;
+    }
+
+     /** @hide */
+    @Nullable
+    public CustomDescription getCustomDescription() {
+        return mCustomDescription;
+    }
+
+    /** @hide */
+    @Nullable
+    public InternalValidator getValidator() {
+        return mValidator;
+    }
+
+    /** @hide */
+    @Nullable
+    public InternalSanitizer[] getSanitizerKeys() {
+        return mSanitizerKeys;
+    }
+
+    /** @hide */
+    @Nullable
+    public AutofillId[][] getSanitizerValues() {
+        return mSanitizerValues;
+    }
+
+    /** @hide */
+    @Nullable
+    public AutofillId getTriggerId() {
+        return mTriggerId;
+    }
+
+    /**
+     * A builder for {@link SaveInfo} objects.
+     */
+    public static final class Builder {
+
+        private final @SaveDataType int mType;
+        private @NegativeButtonStyle int mNegativeButtonStyle = NEGATIVE_BUTTON_STYLE_CANCEL;
+        private @PositiveButtonStyle int mPositiveButtonStyle = POSITIVE_BUTTON_STYLE_SAVE;
+        private IntentSender mNegativeActionListener;
+        private final AutofillId[] mRequiredIds;
+        private AutofillId[] mOptionalIds;
+        private CharSequence mDescription;
+        private boolean mDestroyed;
+        private int mFlags;
+        private CustomDescription mCustomDescription;
+        private InternalValidator mValidator;
+        private ArrayMap<InternalSanitizer, AutofillId[]> mSanitizers;
+        // Set used to validate against duplicate ids.
+        private ArraySet<AutofillId> mSanitizerIds;
+        private AutofillId mTriggerId;
+
+        /**
+         * Creates a new builder.
+         *
+         * @param type the type of information the associated {@link FillResponse} represents. It
+         * can be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
+         * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
+         * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD},
+         * {@link SaveInfo#SAVE_DATA_TYPE_DEBIT_CARD}, {@link SaveInfo#SAVE_DATA_TYPE_PAYMENT_CARD},
+         * {@link SaveInfo#SAVE_DATA_TYPE_GENERIC_CARD}, {@link SaveInfo#SAVE_DATA_TYPE_USERNAME},
+         * or {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
+         * @param requiredIds ids of all required views that will trigger a save request.
+         *
+         * <p>See {@link SaveInfo} for more info.
+         *
+         * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty, or if
+         * it contains any {@code null} entry.
+         */
+        public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) {
+            mType = type;
+            mRequiredIds = assertValid(requiredIds);
+        }
+
+        /**
+         * Creates a new builder when no id is required.
+         *
+         * <p>When using this builder, caller must call {@link #setOptionalIds(AutofillId[])} before
+         * calling {@link #build()}.
+         *
+         * @param type the type of information the associated {@link FillResponse} represents. It
+         * can be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
+         * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
+         * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD},
+         * {@link SaveInfo#SAVE_DATA_TYPE_DEBIT_CARD}, {@link SaveInfo#SAVE_DATA_TYPE_PAYMENT_CARD},
+         * {@link SaveInfo#SAVE_DATA_TYPE_GENERIC_CARD}, {@link SaveInfo#SAVE_DATA_TYPE_USERNAME},
+         * or {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
+         *
+         * <p>See {@link SaveInfo} for more info.
+         */
+        public Builder(@SaveDataType int type) {
+            mType = type;
+            mRequiredIds = null;
+        }
+
+        /**
+         * Sets flags changing the save behavior.
+         *
+         * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE},
+         * {@link #FLAG_DONT_SAVE_ON_FINISH}, {@link #FLAG_DELAY_SAVE}, or {@code 0}.
+         * @return This builder.
+         */
+        public @NonNull Builder setFlags(@SaveInfoFlags int flags) {
+            throwIfDestroyed();
+
+            mFlags = Preconditions.checkFlagsArgument(flags,
+                    FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE | FLAG_DONT_SAVE_ON_FINISH
+                            | FLAG_DELAY_SAVE);
+            return this;
+        }
+
+        /**
+         * Sets the ids of additional, optional views the service would be interested to save.
+         *
+         * <p>See {@link SaveInfo} for more info.
+         *
+         * @param ids The ids of the optional views.
+         * @return This builder.
+         *
+         * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if
+         * it contains any {@code null} entry.
+         */
+        public @NonNull Builder setOptionalIds(@NonNull AutofillId[] ids) {
+            throwIfDestroyed();
+            mOptionalIds = assertValid(ids);
+            return this;
+        }
+
+        /**
+         * Sets an optional description to be shown in the UI when the user is asked to save.
+         *
+         * <p>Typically, it describes how the data will be stored by the service, so it can help
+         * users to decide whether they can trust the service to save their data.
+         *
+         * @param description a succint description.
+         * @return This Builder.
+         *
+         * @throws IllegalStateException if this call was made after calling
+         * {@link #setCustomDescription(CustomDescription)}.
+         */
+        public @NonNull Builder setDescription(@Nullable CharSequence description) {
+            throwIfDestroyed();
+            Preconditions.checkState(mCustomDescription == null,
+                    "Can call setDescription() or setCustomDescription(), but not both");
+            mDescription = description;
+            return this;
+        }
+
+        /**
+         * Sets a custom description to be shown in the UI when the user is asked to save.
+         *
+         * <p>Typically used when the service must show more info about the object being saved,
+         * like a credit card logo, masked number, and expiration date.
+         *
+         * @param customDescription the custom description.
+         * @return This Builder.
+         *
+         * @throws IllegalStateException if this call was made after calling
+         * {@link #setDescription(CharSequence)}.
+         */
+        public @NonNull Builder setCustomDescription(@NonNull CustomDescription customDescription) {
+            throwIfDestroyed();
+            Preconditions.checkState(mDescription == null,
+                    "Can call setDescription() or setCustomDescription(), but not both");
+            mCustomDescription = customDescription;
+            return this;
+        }
+
+        /**
+         * Sets the style and listener for the negative save action.
+         *
+         * <p>This allows an autofill service to customize the style and be
+         * notified when the user selects the negative action in the save
+         * UI. Note that selecting the negative action regardless of its style
+         * and listener being customized would dismiss the save UI and if a
+         * custom listener intent is provided then this intent is
+         * started. The default style is {@link #NEGATIVE_BUTTON_STYLE_CANCEL}</p>
+         *
+         * @param style The action style.
+         * @param listener The action listener.
+         * @return This builder.
+         *
+         * @see #NEGATIVE_BUTTON_STYLE_CANCEL
+         * @see #NEGATIVE_BUTTON_STYLE_REJECT
+         * @see #NEGATIVE_BUTTON_STYLE_NEVER
+         *
+         * @throws IllegalArgumentException If the style is invalid
+         */
+        public @NonNull Builder setNegativeAction(@NegativeButtonStyle int style,
+                @Nullable IntentSender listener) {
+            throwIfDestroyed();
+            Preconditions.checkArgumentInRange(style, NEGATIVE_BUTTON_STYLE_CANCEL,
+                    NEGATIVE_BUTTON_STYLE_NEVER, "style");
+            mNegativeButtonStyle = style;
+            mNegativeActionListener = listener;
+            return this;
+        }
+
+        /**
+         * Sets the style for the positive save action.
+         *
+         * <p>This allows an autofill service to customize the style of the
+         * positive action in the save UI. Note that selecting the positive
+         * action regardless of its style would dismiss the save UI and calling
+         * into the {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback) save request}.
+         * The service should take the next action if selecting style
+         * {@link #POSITIVE_BUTTON_STYLE_CONTINUE}. The default style is
+         * {@link #POSITIVE_BUTTON_STYLE_SAVE}
+         *
+         * @param style The action style.
+         * @return This builder.
+         *
+         * @see #POSITIVE_BUTTON_STYLE_SAVE
+         * @see #POSITIVE_BUTTON_STYLE_CONTINUE
+         *
+         * @throws IllegalArgumentException If the style is invalid
+         */
+        public @NonNull Builder setPositiveAction(@PositiveButtonStyle int style) {
+            throwIfDestroyed();
+            Preconditions.checkArgumentInRange(style, POSITIVE_BUTTON_STYLE_SAVE,
+                    POSITIVE_BUTTON_STYLE_CONTINUE, "style");
+            mPositiveButtonStyle = style;
+            return this;
+        }
+
+        /**
+         * Sets an object used to validate the user input - if the input is not valid, the
+         * autofill save UI is not shown.
+         *
+         * <p>Typically used to validate credit card numbers. Examples:
+         *
+         * <p>Validator for a credit number that must have exactly 16 digits:
+         *
+         * <pre class="prettyprint">
+         * Validator validator = new RegexValidator(ccNumberId, Pattern.compile(""^\\d{16}$"))
+         * </pre>
+         *
+         * <p>Validator for a credit number that must pass a Luhn checksum and either have
+         * 16 digits, or 15 digits starting with 108:
+         *
+         * <pre class="prettyprint">
+         * import static android.service.autofill.Validators.and;
+         * import static android.service.autofill.Validators.or;
+         *
+         * Validator validator =
+         *   and(
+         *     new LuhnChecksumValidator(ccNumberId),
+         *     or(
+         *       new RegexValidator(ccNumberId, Pattern.compile("^\\d{16}$")),
+         *       new RegexValidator(ccNumberId, Pattern.compile("^108\\d{12}$"))
+         *     )
+         *   );
+         * </pre>
+         *
+         * <p><b>Note:</b> the example above is just for illustrative purposes; the same validator
+         * could be created using a single regex for the {@code OR} part:
+         *
+         * <pre class="prettyprint">
+         * Validator validator =
+         *   and(
+         *     new LuhnChecksumValidator(ccNumberId),
+         *     new RegexValidator(ccNumberId, Pattern.compile(""^(\\d{16}|108\\d{12})$"))
+         *   );
+         * </pre>
+         *
+         * <p>Validator for a credit number contained in just 4 fields and that must have exactly
+         * 4 digits on each field:
+         *
+         * <pre class="prettyprint">
+         * import static android.service.autofill.Validators.and;
+         *
+         * Validator validator =
+         *   and(
+         *     new RegexValidator(ccNumberId1, Pattern.compile("^\\d{4}$")),
+         *     new RegexValidator(ccNumberId2, Pattern.compile("^\\d{4}$")),
+         *     new RegexValidator(ccNumberId3, Pattern.compile("^\\d{4}$")),
+         *     new RegexValidator(ccNumberId4, Pattern.compile("^\\d{4}$"))
+         *   );
+         * </pre>
+         *
+         * @param validator an implementation provided by the Android System.
+         * @return this builder.
+         *
+         * @throws IllegalArgumentException if {@code validator} is not a class provided
+         * by the Android System.
+         */
+        public @NonNull Builder setValidator(@NonNull Validator validator) {
+            throwIfDestroyed();
+            Preconditions.checkArgument((validator instanceof InternalValidator),
+                    "not provided by Android System: " + validator);
+            mValidator = (InternalValidator) validator;
+            return this;
+        }
+
+        /**
+         * Adds a sanitizer for one or more field.
+         *
+         * <p>When a sanitizer is set for a field, the {@link AutofillValue} is sent to the
+         * sanitizer before a save request is <a href="#TriggeringSaveRequest">triggered</a>.
+         *
+         * <p>Typically used to avoid displaying the save UI for values that are autofilled but
+         * reformattedby the app. For example, to remove spaces between every 4 digits of a
+         * credit card number:
+         *
+         * <pre class="prettyprint">
+         * builder.addSanitizer(new TextValueSanitizer(
+         *     Pattern.compile("^(\\d{4})\\s?(\\d{4})\\s?(\\d{4})\\s?(\\d{4})$", "$1$2$3$4")),
+         *     ccNumberId);
+         * </pre>
+         *
+         * <p>The same sanitizer can be reused to sanitize multiple fields. For example, to trim
+         * both the username and password fields:
+         *
+         * <pre class="prettyprint">
+         * builder.addSanitizer(
+         *     new TextValueSanitizer(Pattern.compile("^\\s*(.*)\\s*$"), "$1"),
+         *         usernameId, passwordId);
+         * </pre>
+         *
+         * <p>The sanitizer can also be used as an alternative for a
+         * {@link #setValidator(Validator) validator}. If any of the {@code ids} is a
+         * {@link #Builder(int, AutofillId[]) required id} and the {@code sanitizer} fails
+         * because of it, then the save UI is not shown.
+         *
+         * @param sanitizer an implementation provided by the Android System.
+         * @param ids id of fields whose value will be sanitized.
+         * @return this builder.
+         *
+         * @throws IllegalArgumentException if a sanitizer for any of the {@code ids} has already
+         * been added or if {@code ids} is empty.
+         */
+        public @NonNull Builder addSanitizer(@NonNull Sanitizer sanitizer,
+                @NonNull AutofillId... ids) {
+            throwIfDestroyed();
+            Preconditions.checkArgument(!ArrayUtils.isEmpty(ids), "ids cannot be empty or null");
+            Preconditions.checkArgument((sanitizer instanceof InternalSanitizer),
+                    "not provided by Android System: " + sanitizer);
+
+            if (mSanitizers == null) {
+                mSanitizers = new ArrayMap<>();
+                mSanitizerIds = new ArraySet<>(ids.length);
+            }
+
+            // Check for duplicates first.
+            for (AutofillId id : ids) {
+                Preconditions.checkArgument(!mSanitizerIds.contains(id), "already added %s", id);
+                mSanitizerIds.add(id);
+            }
+
+            mSanitizers.put((InternalSanitizer) sanitizer, ids);
+
+            return this;
+        }
+
+       /**
+         * Explicitly defines the view that should commit the autofill context when clicked.
+         *
+         * <p>Usually, the save request is only automatically
+         * <a href="#TriggeringSaveRequest">triggered</a> after the activity is
+         * finished or all relevant views become invisible, but there are scenarios where the
+         * autofill context is automatically commited too late
+         * &mdash;for example, when the activity manually clears the autofillable views when a
+         * button is tapped. This method can be used to trigger the autofill save UI earlier in
+         * these scenarios.
+         *
+         * <p><b>Note:</b> This method should only be used in scenarios where the automatic workflow
+         * is not enough, otherwise it could trigger the autofill save UI when it should not&mdash;
+         * for example, when the user entered invalid credentials for the autofillable views.
+         */
+        public @NonNull Builder setTriggerId(@NonNull AutofillId id) {
+            throwIfDestroyed();
+            mTriggerId = Preconditions.checkNotNull(id);
+            return this;
+        }
+
+        /**
+         * Builds a new {@link SaveInfo} instance.
+         *
+         * @throws IllegalStateException if no
+         * {@link #Builder(int, AutofillId[]) required ids},
+         * or {@link #setOptionalIds(AutofillId[]) optional ids}, or {@link #FLAG_DELAY_SAVE}
+         * were set
+         */
+        public SaveInfo build() {
+            throwIfDestroyed();
+            Preconditions.checkState(
+                    !ArrayUtils.isEmpty(mRequiredIds) || !ArrayUtils.isEmpty(mOptionalIds)
+                            || (mFlags & FLAG_DELAY_SAVE) != 0,
+                    "must have at least one required or optional id or FLAG_DELAYED_SAVE");
+            mDestroyed = true;
+            return new SaveInfo(this);
+        }
+
+        private void throwIfDestroyed() {
+            if (mDestroyed) {
+                throw new IllegalStateException("Already called #build()");
+            }
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        final StringBuilder builder = new StringBuilder("SaveInfo: [type=")
+                .append(DebugUtils.flagsToString(SaveInfo.class, "SAVE_DATA_TYPE_", mType))
+                .append(", requiredIds=").append(Arrays.toString(mRequiredIds))
+                .append(", negative style=").append(DebugUtils.flagsToString(SaveInfo.class,
+                        "NEGATIVE_BUTTON_STYLE_", mNegativeButtonStyle))
+                .append(", positive style=").append(DebugUtils.flagsToString(SaveInfo.class,
+                        "POSITIVE_BUTTON_STYLE_", mPositiveButtonStyle));
+        if (mOptionalIds != null) {
+            builder.append(", optionalIds=").append(Arrays.toString(mOptionalIds));
+        }
+        if (mDescription != null) {
+            builder.append(", description=").append(mDescription);
+        }
+        if (mFlags != 0) {
+            builder.append(", flags=").append(mFlags);
+        }
+        if (mCustomDescription != null) {
+            builder.append(", customDescription=").append(mCustomDescription);
+        }
+        if (mValidator != null) {
+            builder.append(", validator=").append(mValidator);
+        }
+        if (mSanitizerKeys != null) {
+            builder.append(", sanitizerKeys=").append(mSanitizerKeys.length);
+        }
+        if (mSanitizerValues != null) {
+            builder.append(", sanitizerValues=").append(mSanitizerValues.length);
+        }
+        if (mTriggerId != null) {
+            builder.append(", triggerId=").append(mTriggerId);
+        }
+
+        return builder.append("]").toString();
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(mType);
+        parcel.writeParcelableArray(mRequiredIds, flags);
+        parcel.writeParcelableArray(mOptionalIds, flags);
+        parcel.writeInt(mNegativeButtonStyle);
+        parcel.writeParcelable(mNegativeActionListener, flags);
+        parcel.writeInt(mPositiveButtonStyle);
+        parcel.writeCharSequence(mDescription);
+        parcel.writeParcelable(mCustomDescription, flags);
+        parcel.writeParcelable(mValidator, flags);
+        parcel.writeParcelableArray(mSanitizerKeys, flags);
+        if (mSanitizerKeys != null) {
+            for (int i = 0; i < mSanitizerValues.length; i++) {
+                parcel.writeParcelableArray(mSanitizerValues[i], flags);
+            }
+        }
+        parcel.writeParcelable(mTriggerId, flags);
+        parcel.writeInt(mFlags);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() {
+        @Override
+        public SaveInfo 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 int type = parcel.readInt();
+            final AutofillId[] requiredIds = parcel.readParcelableArray(null, AutofillId.class);
+            final Builder builder = requiredIds != null
+                    ? new Builder(type, requiredIds)
+                    : new Builder(type);
+            final AutofillId[] optionalIds = parcel.readParcelableArray(null, AutofillId.class);
+            if (optionalIds != null) {
+                builder.setOptionalIds(optionalIds);
+            }
+
+            builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null));
+            builder.setPositiveAction(parcel.readInt());
+            builder.setDescription(parcel.readCharSequence());
+            final CustomDescription customDescripton = parcel.readParcelable(null);
+            if (customDescripton != null) {
+                builder.setCustomDescription(customDescripton);
+            }
+            final InternalValidator validator = parcel.readParcelable(null);
+            if (validator != null) {
+                builder.setValidator(validator);
+            }
+            final InternalSanitizer[] sanitizers =
+                    parcel.readParcelableArray(null, InternalSanitizer.class);
+            if (sanitizers != null) {
+                final int size = sanitizers.length;
+                for (int i = 0; i < size; i++) {
+                    final AutofillId[] autofillIds =
+                            parcel.readParcelableArray(null, AutofillId.class);
+                    builder.addSanitizer(sanitizers[i], autofillIds);
+                }
+            }
+            final AutofillId triggerId = parcel.readParcelable(null);
+            if (triggerId != null) {
+                builder.setTriggerId(triggerId);
+            }
+            builder.setFlags(parcel.readInt());
+            return builder.build();
+        }
+
+        @Override
+        public SaveInfo[] newArray(int size) {
+            return new SaveInfo[size];
+        }
+    };
+}
diff --git a/android/service/autofill/SaveRequest.java b/android/service/autofill/SaveRequest.java
new file mode 100644
index 0000000..5dd07c4
--- /dev/null
+++ b/android/service/autofill/SaveRequest.java
@@ -0,0 +1,116 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents a request to an {@link AutofillService
+ * autofill provider} to save applicable data entered by the user.
+ *
+ * @see AutofillService#onSaveRequest(SaveRequest, SaveCallback)
+ */
+public final class SaveRequest implements Parcelable {
+    private final @NonNull ArrayList<FillContext> mFillContexts;
+    private final @Nullable Bundle mClientState;
+    private final @Nullable ArrayList<String> mDatasetIds;
+
+    /** @hide */
+    public SaveRequest(@NonNull ArrayList<FillContext> fillContexts,
+            @Nullable Bundle clientState, @Nullable ArrayList<String> datasetIds) {
+        mFillContexts = Preconditions.checkNotNull(fillContexts, "fillContexts");
+        mClientState = clientState;
+        mDatasetIds = datasetIds;
+    }
+
+    private SaveRequest(@NonNull Parcel parcel) {
+        this(parcel.createTypedArrayList(FillContext.CREATOR),
+                parcel.readBundle(), parcel.createStringArrayList());
+    }
+
+    /**
+     * Gets the contexts associated with each previous fill request.
+     *
+     * <p><b>Note:</b> Starting on Android {@link android.os.Build.VERSION_CODES#Q}, it could also
+     * include contexts from requests whose {@link SaveInfo} had the
+     * {@link SaveInfo#FLAG_DELAY_SAVE} flag.
+     *
+     * @return The contexts associated with each previous fill request.
+     */
+    public @NonNull List<FillContext> getFillContexts() {
+        return mFillContexts;
+    }
+
+    /**
+     * Gets the latest client state bundle set by the service in a
+     * {@link FillResponse.Builder#setClientState(Bundle) fill response}.
+     *
+     * <p><b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, only client state
+     * bundles set by {@link FillResponse.Builder#setClientState(Bundle)} were considered. On
+     * Android {@link android.os.Build.VERSION_CODES#P} and higher, bundles set in the result of
+     * an authenticated request through the
+     * {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE} extra are
+     * also considered (and take precedence when set).
+     *
+     * @return The client state.
+     */
+    public @Nullable Bundle getClientState() {
+        return mClientState;
+    }
+
+    /**
+     * Gets the ids of the datasets selected by the user, in the order in which they were selected.
+     */
+    @Nullable
+    public List<String> getDatasetIds() {
+        return mDatasetIds;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeTypedList(mFillContexts, flags);
+        parcel.writeBundle(mClientState);
+        parcel.writeStringList(mDatasetIds);
+    }
+
+    public static final @android.annotation.NonNull Creator<SaveRequest> CREATOR =
+            new Creator<SaveRequest>() {
+        @Override
+        public SaveRequest createFromParcel(Parcel parcel) {
+            return new SaveRequest(parcel);
+        }
+
+        @Override
+        public SaveRequest[] newArray(int size) {
+            return new SaveRequest[size];
+        }
+    };
+}
diff --git a/android/service/autofill/TextValueSanitizer.java b/android/service/autofill/TextValueSanitizer.java
new file mode 100644
index 0000000..cc48fcb
--- /dev/null
+++ b/android/service/autofill/TextValueSanitizer.java
@@ -0,0 +1,131 @@
+/*
+ * 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;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Sanitizes a text {@link AutofillValue} using a regular expression (regex) substitution.
+ *
+ * <p>For example, to remove spaces from groups of 4-digits in a credit card:
+ *
+ * <pre class="prettyprint">
+ * new TextValueSanitizer(Pattern.compile("^(\\d{4})\\s?(\\d{4})\\s?(\\d{4})\\s?(\\d{4})$"),
+ *     "$1$2$3$4")
+ * </pre>
+ */
+public final class TextValueSanitizer extends InternalSanitizer implements
+        Sanitizer, Parcelable {
+    private static final String TAG = "TextValueSanitizer";
+
+    private final Pattern mRegex;
+    private final String mSubst;
+
+    /**
+     * Default constructor.
+     *
+     * @param regex regular expression with groups (delimited by {@code (} and {@code (}) that
+     * are used to substitute parts of the {@link AutofillValue#getTextValue() text value}.
+     * @param subst the string that substitutes the matched regex, using {@code $} for
+     * group substitution ({@code $1} for 1st group match, {@code $2} for 2nd, etc).
+     */
+    public TextValueSanitizer(@NonNull Pattern regex, @NonNull String subst) {
+        mRegex = Preconditions.checkNotNull(regex);
+        mSubst = Preconditions.checkNotNull(subst);
+    }
+
+    /** @hide */
+    @Override
+    @TestApi
+    @Nullable
+    public AutofillValue sanitize(@NonNull AutofillValue value) {
+        if (value == null) {
+            Slog.w(TAG, "sanitize() called with null value");
+            return null;
+        }
+        if (!value.isText()) {
+            if (sDebug) Slog.d(TAG, "sanitize() called with non-text value: " + value);
+            return null;
+        }
+
+        final CharSequence text = value.getTextValue();
+
+        try {
+            final Matcher matcher = mRegex.matcher(text);
+            if (!matcher.matches()) {
+                if (sDebug) Slog.d(TAG, "sanitize(): " + mRegex + " failed for " + value);
+                return null;
+            }
+
+            final CharSequence sanitized = matcher.replaceAll(mSubst);
+            return AutofillValue.forText(sanitized);
+        } catch (Exception e) {
+            Slog.w(TAG, "Exception evaluating " + mRegex + "/" + mSubst + ": " + e);
+            return null;
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "TextValueSanitizer: [regex=" + mRegex + ", subst=" + mSubst + "]";
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeSerializable(mRegex);
+        parcel.writeString(mSubst);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<TextValueSanitizer> CREATOR =
+            new Parcelable.Creator<TextValueSanitizer>() {
+        @Override
+        public TextValueSanitizer createFromParcel(Parcel parcel) {
+            return new TextValueSanitizer((Pattern) parcel.readSerializable(), parcel.readString());
+        }
+
+        @Override
+        public TextValueSanitizer[] newArray(int size) {
+            return new TextValueSanitizer[size];
+        }
+    };
+}
diff --git a/android/service/autofill/Transformation.java b/android/service/autofill/Transformation.java
new file mode 100644
index 0000000..de43955
--- /dev/null
+++ b/android/service/autofill/Transformation.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * Helper class used to change a child view of a {@link android.widget.RemoteViews presentation
+ * template} at runtime, using the values of fields contained in the screen.
+ *
+ * <p>Typically used by {@link CustomDescription} to provide a customized autofill save UI.
+ *
+ * <p><b>Note:</b> This interface is not meant to be implemented by app developers; only
+ * implementations provided by the Android System can be used in other Autofill APIs.
+ */
+public interface Transformation {
+}
diff --git a/android/service/autofill/UserData.java b/android/service/autofill/UserData.java
new file mode 100644
index 0000000..7814f70
--- /dev/null
+++ b/android/service/autofill/UserData.java
@@ -0,0 +1,537 @@
+/*
+ * 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_CATEGORY_COUNT;
+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.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Settings;
+import android.service.autofill.FieldClassification.Match;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.Helper;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Defines the user data used for
+ * <a href="AutofillService.html#FieldClassification">field classification</a>.
+ */
+public final class UserData implements FieldClassificationUserData, Parcelable {
+
+    private static final String TAG = "UserData";
+
+    private static final int DEFAULT_MAX_USER_DATA_SIZE = 50;
+    private static final int DEFAULT_MAX_CATEGORY_COUNT = 10;
+    private static final int DEFAULT_MAX_FIELD_CLASSIFICATION_IDS_SIZE = 10;
+    private static final int DEFAULT_MIN_VALUE_LENGTH = 3;
+    private static final int DEFAULT_MAX_VALUE_LENGTH = 100;
+
+    private final String mId;
+    private final String[] mCategoryIds;
+    private final String[] mValues;
+
+    private final String mDefaultAlgorithm;
+    private final Bundle mDefaultArgs;
+    private final ArrayMap<String, String> mCategoryAlgorithms;
+    private final ArrayMap<String, Bundle> mCategoryArgs;
+
+    private UserData(Builder builder) {
+        mId = builder.mId;
+        mCategoryIds = new String[builder.mCategoryIds.size()];
+        builder.mCategoryIds.toArray(mCategoryIds);
+        mValues = new String[builder.mValues.size()];
+        builder.mValues.toArray(mValues);
+        builder.mValues.toArray(mValues);
+
+        mDefaultAlgorithm = builder.mDefaultAlgorithm;
+        mDefaultArgs = builder.mDefaultArgs;
+        mCategoryAlgorithms = builder.mCategoryAlgorithms;
+        mCategoryArgs = builder.mCategoryArgs;
+    }
+
+    /**
+     * Gets the name of the default algorithm that is used to calculate
+     * {@link Match#getScore()} match scores}.
+     */
+    @Nullable
+    @Override
+    public String getFieldClassificationAlgorithm() {
+        return mDefaultAlgorithm;
+    }
+
+    /** @hide */
+    @Override
+    public Bundle getDefaultFieldClassificationArgs() {
+        return mDefaultArgs;
+    }
+
+    /**
+     * Gets the name of the algorithm corresponding to the specific autofill category
+     * that is used to calculate {@link Match#getScore() match scores}
+     *
+     * @param categoryId autofill field category
+     *
+     * @return String name of algorithm, null if none found.
+     */
+    @Nullable
+    @Override
+    public String getFieldClassificationAlgorithmForCategory(@NonNull String categoryId) {
+        Preconditions.checkNotNull(categoryId);
+        if (mCategoryAlgorithms == null || !mCategoryAlgorithms.containsKey(categoryId)) {
+            return null;
+        }
+        return mCategoryAlgorithms.get(categoryId);
+    }
+
+    /**
+     * Gets the id.
+     */
+    public String getId() {
+        return mId;
+    }
+
+    /** @hide */
+    @Override
+    public String[] getCategoryIds() {
+        return mCategoryIds;
+    }
+
+    /** @hide */
+    @Override
+    public String[] getValues() {
+        return mValues;
+    }
+
+    /** @hide */
+    @TestApi
+    @Override
+    public ArrayMap<String, String> getFieldClassificationAlgorithms() {
+        return mCategoryAlgorithms;
+    }
+
+    /** @hide */
+    @Override
+    public ArrayMap<String, Bundle> getFieldClassificationArgs() {
+        return mCategoryArgs;
+    }
+
+    /** @hide */
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("id: "); pw.print(mId);
+        pw.print(prefix); pw.print("Default Algorithm: "); pw.print(mDefaultAlgorithm);
+        pw.print(prefix); pw.print("Default Args"); pw.print(mDefaultArgs);
+        if (mCategoryAlgorithms != null && mCategoryAlgorithms.size() > 0) {
+            pw.print(prefix); pw.print("Algorithms per category: ");
+            for (int i = 0; i < mCategoryAlgorithms.size(); i++) {
+                pw.print(prefix); pw.print(prefix); pw.print(mCategoryAlgorithms.keyAt(i));
+                pw.print(": "); pw.println(Helper.getRedacted(mCategoryAlgorithms.valueAt(i)));
+                pw.print("args="); pw.print(mCategoryArgs.get(mCategoryAlgorithms.keyAt(i)));
+            }
+        }
+        // Cannot disclose field ids or values because they could contain PII
+        pw.print(prefix); pw.print("Field ids size: "); pw.println(mCategoryIds.length);
+        for (int i = 0; i < mCategoryIds.length; i++) {
+            pw.print(prefix); pw.print(prefix); pw.print(i); pw.print(": ");
+            pw.println(Helper.getRedacted(mCategoryIds[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(Helper.getRedacted(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("maxCategoryCount: "); pw.println(getMaxCategoryCount());
+        pw.print(prefix); pw.print("minValueLength: "); pw.println(getMinValueLength());
+        pw.print(prefix); pw.print("maxValueLength: "); pw.println(getMaxValueLength());
+    }
+
+    /**
+     * A builder for {@link UserData} objects.
+     */
+    public static final class Builder {
+        private final String mId;
+        private final ArrayList<String> mCategoryIds;
+        private final ArrayList<String> mValues;
+        private String mDefaultAlgorithm;
+        private Bundle mDefaultArgs;
+
+        // Map of autofill field categories to fleid classification algorithms and args
+        private ArrayMap<String, String> mCategoryAlgorithms;
+        private ArrayMap<String, Bundle> mCategoryArgs;
+
+        private boolean mDestroyed;
+
+        // Non-persistent array used to limit the number of unique ids.
+        private final ArraySet<String> mUniqueCategoryIds;
+        // Non-persistent array used to ignore duplaicated value/category pairs.
+        private final ArraySet<String> mUniqueValueCategoryPairs;
+
+        /**
+         * Creates a new builder for the user data used for <a href="#FieldClassification">field
+         * classification</a>.
+         *
+         * <p>The user data must contain at least one pair of {@code value} -> {@code categoryId},
+         * and more pairs can be added through the {@link #add(String, String)} method. For example:
+         *
+         * <pre class="prettyprint">
+         * new UserData.Builder("v1", "Bart Simpson", "name")
+         *   .add("[email protected]", "email")
+         *   .add("[email protected]", "email")
+         *   .build();
+         * </pre>
+         *
+         * @param id id used to identify the whole {@link UserData} object. This id is also returned
+         * by {@link AutofillManager#getUserDataId()}, which can be used to check if the
+         * {@link UserData} is up-to-date without fetching the whole object (through
+         * {@link AutofillManager#getUserData()}).
+         *
+         * @param value value of the user data.
+         * @param categoryId autofill field category.
+         *
+         * @throws IllegalArgumentException if any of the following occurs:
+         * <ul>
+         *   <li>{@code id} is empty</li>
+         *   <li>{@code categoryId} is empty</li>
+         *   <li>{@code value} is empty</li>
+         *   <li>the length of {@code value} is lower than {@link UserData#getMinValueLength()}</li>
+         *   <li>the length of {@code value} is higher than
+         *       {@link UserData#getMaxValueLength()}</li>
+         * </ul>
+         */
+        public Builder(@NonNull String id, @NonNull String value, @NonNull String categoryId) {
+            mId = checkNotEmpty("id", id);
+            checkNotEmpty("categoryId", categoryId);
+            checkValidValue(value);
+            final int maxUserDataSize = getMaxUserDataSize();
+            mCategoryIds = new ArrayList<>(maxUserDataSize);
+            mValues = new ArrayList<>(maxUserDataSize);
+            mUniqueValueCategoryPairs = new ArraySet<>(maxUserDataSize);
+
+            mUniqueCategoryIds = new ArraySet<>(getMaxCategoryCount());
+
+            addMapping(value, categoryId);
+        }
+
+        /**
+         * Sets the default algorithm used for
+         * <a href="#FieldClassification">field classification</a>.
+         *
+         * <p>The currently available algorithms can be retrieve through
+         * {@link AutofillManager#getAvailableFieldClassificationAlgorithms()}.
+         *
+         * <p>If not set, the
+         * {@link AutofillManager#getDefaultFieldClassificationAlgorithm() default algorithm} is
+         * used instead.
+         *
+         * @param name name of the algorithm or {@code null} to used default.
+         * @param args optional arguments to the algorithm.
+         *
+         * @return this builder
+         */
+        @NonNull
+        public Builder setFieldClassificationAlgorithm(@Nullable String name,
+                @Nullable Bundle args) {
+            throwIfDestroyed();
+            mDefaultAlgorithm = name;
+            mDefaultArgs = args;
+            return this;
+        }
+
+        /**
+         * Sets the algorithm used for <a href="#FieldClassification">field classification</a>
+         * for the specified category.
+         *
+         * <p>The currently available algorithms can be retrieved through
+         * {@link AutofillManager#getAvailableFieldClassificationAlgorithms()}.
+         *
+         * <p>If not set, the
+         * {@link AutofillManager#getDefaultFieldClassificationAlgorithm() default algorithm} is
+         * used instead.
+         *
+         * @param categoryId autofill field category.
+         * @param name name of the algorithm or {@code null} to used default.
+         * @param args optional arguments to the algorithm.
+         *
+         * @return this builder
+         */
+        @NonNull
+        public Builder setFieldClassificationAlgorithmForCategory(@NonNull String categoryId,
+                @Nullable String name, @Nullable Bundle args) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(categoryId);
+            if (mCategoryAlgorithms == null) {
+                mCategoryAlgorithms = new ArrayMap<>(getMaxCategoryCount());
+            }
+            if (mCategoryArgs == null) {
+                mCategoryArgs = new ArrayMap<>(getMaxCategoryCount());
+            }
+            mCategoryAlgorithms.put(categoryId, name);
+            mCategoryArgs.put(categoryId, args);
+            return this;
+        }
+
+        /**
+         * Adds a new value for user data.
+         *
+         * @param value value of the user data.
+         * @param categoryId string used to identify the category the value is associated with.
+         *
+         * @throws IllegalStateException if:
+         * <ul>
+         *   <li>{@link #build()} already called</li>
+         *   <li>the {@code value} has already been added (<b>Note: </b> this restriction was
+         *   lifted on Android {@link android.os.Build.VERSION_CODES#Q} and later)</li>
+         *   <li>the number of unique {@code categoryId} values added so far is more than
+         *       {@link UserData#getMaxCategoryCount()}</li>
+         *   <li>the number of {@code values} added so far is is more than
+         *       {@link UserData#getMaxUserDataSize()}</li>
+         * </ul>
+         *
+         * @throws IllegalArgumentException if any of the following occurs:
+         * <ul>
+         *   <li>{@code id} is empty</li>
+         *   <li>{@code categoryId} is empty</li>
+         *   <li>{@code value} is empty</li>
+         *   <li>the length of {@code value} is lower than {@link UserData#getMinValueLength()}</li>
+         *   <li>the length of {@code value} is higher than
+         *       {@link UserData#getMaxValueLength()}</li>
+         * </ul>
+         */
+        @NonNull
+        public Builder add(@NonNull String value, @NonNull String categoryId) {
+            throwIfDestroyed();
+            checkNotEmpty("categoryId", categoryId);
+            checkValidValue(value);
+
+            if (!mUniqueCategoryIds.contains(categoryId)) {
+                // New category - check size
+                Preconditions.checkState(mUniqueCategoryIds.size() < getMaxCategoryCount(),
+                        "already added " + mUniqueCategoryIds.size() + " unique category ids");
+            }
+
+            Preconditions.checkState(mValues.size() < getMaxUserDataSize(),
+                    "already added " + mValues.size() + " elements");
+            addMapping(value, categoryId);
+
+            return this;
+        }
+
+        private void addMapping(@NonNull String value, @NonNull String categoryId) {
+            final String pair = value + ":" + categoryId;
+            if (mUniqueValueCategoryPairs.contains(pair)) {
+                // Don't include value on message because it could contain PII
+                Log.w(TAG, "Ignoring entry with same value / category");
+                return;
+            }
+            mCategoryIds.add(categoryId);
+            mValues.add(value);
+            mUniqueCategoryIds.add(categoryId);
+            mUniqueValueCategoryPairs.add(pair);
+        }
+
+        private String checkNotEmpty(@NonNull String name, @Nullable String value) {
+            Preconditions.checkNotNull(value);
+            Preconditions.checkArgument(!TextUtils.isEmpty(value), "%s cannot be empty", name);
+            return value;
+        }
+
+        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.
+         */
+        @NonNull
+        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();
+
+        final StringBuilder builder = new StringBuilder("UserData: [id=").append(mId);
+        // Cannot disclose category ids or values because they could contain PII
+        builder.append(", categoryIds=");
+        Helper.appendRedacted(builder, mCategoryIds);
+        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.writeString(mId);
+        parcel.writeStringArray(mCategoryIds);
+        parcel.writeStringArray(mValues);
+        parcel.writeString(mDefaultAlgorithm);
+        parcel.writeBundle(mDefaultArgs);
+        parcel.writeMap(mCategoryAlgorithms);
+        parcel.writeMap(mCategoryArgs);
+    }
+
+    public static final @android.annotation.NonNull 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 id = parcel.readString();
+            final String[] categoryIds = parcel.readStringArray();
+            final String[] values = parcel.readStringArray();
+            final String defaultAlgorithm = parcel.readString();
+            final Bundle defaultArgs = parcel.readBundle();
+            final ArrayMap<String, String> categoryAlgorithms = new ArrayMap<>();
+            parcel.readMap(categoryAlgorithms, String.class.getClassLoader());
+            final ArrayMap<String, Bundle> categoryArgs = new ArrayMap<>();
+            parcel.readMap(categoryArgs, Bundle.class.getClassLoader());
+
+            final Builder builder = new Builder(id, values[0], categoryIds[0])
+                    .setFieldClassificationAlgorithm(defaultAlgorithm, defaultArgs);
+
+            for (int i = 1; i < categoryIds.length; i++) {
+                String categoryId = categoryIds[i];
+                builder.add(values[i], categoryId);
+            }
+
+            final int size = categoryAlgorithms.size();
+            if (size > 0) {
+                for (int i = 0; i < size; i++) {
+                    final String categoryId = categoryAlgorithms.keyAt(i);
+                    builder.setFieldClassificationAlgorithmForCategory(categoryId,
+                            categoryAlgorithms.valueAt(i), categoryArgs.get(categoryId));
+                }
+            }
+            return builder.build();
+        }
+
+        @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 maximum number of unique category ids that can be passed to
+     * the builder's constructor and {@link Builder#add(String, String)}.
+     */
+    public static int getMaxCategoryCount() {
+        return getInt(AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, DEFAULT_MAX_CATEGORY_COUNT);
+    }
+
+    /**
+     * Gets the minimum length of values passed to the builder's constructor or
+     * or {@link Builder#add(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 the builder's constructor or
+     * or {@link Builder#add(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/android/service/autofill/Validator.java b/android/service/autofill/Validator.java
new file mode 100644
index 0000000..a4036f2
--- /dev/null
+++ b/android/service/autofill/Validator.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+/**
+ * Class used to define whether a condition is satisfied.
+ *
+ * <p>Typically used to avoid displaying the save UI when the user input is invalid.
+ */
+public interface Validator {
+}
diff --git a/android/service/autofill/Validators.java b/android/service/autofill/Validators.java
new file mode 100644
index 0000000..0f1ba98
--- /dev/null
+++ b/android/service/autofill/Validators.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Factory for {@link Validator} operations.
+ *
+ * <p>See {@link SaveInfo.Builder#setValidator(Validator)} for examples.
+ */
+public final class Validators {
+
+    private Validators() {
+        throw new UnsupportedOperationException("contains static methods only");
+    }
+
+    /**
+     * Creates a validator that is only valid if all {@code validators} are valid.
+     *
+     * <p>Used to represent an {@code AND} boolean operation in a chain of validators.
+     *
+     * @throws IllegalArgumentException if any element of {@code validators} is an instance of a
+     * class that is not provided by the Android System.
+     */
+    @NonNull
+    public static Validator and(@NonNull Validator...validators) {
+        return new RequiredValidators(getInternalValidators(validators));
+    }
+
+    /**
+     * Creates a validator that is valid if any of the {@code validators} is valid.
+     *
+     * <p>Used to represent an {@code OR} boolean operation in a chain of validators.
+     *
+     * @throws IllegalArgumentException if any element of {@code validators} is an instance of a
+     * class that is not provided by the Android System.
+     */
+    @NonNull
+    public static Validator or(@NonNull Validator...validators) {
+        return new OptionalValidators(getInternalValidators(validators));
+    }
+
+    /**
+     * Creates a validator that is valid when {@code validator} is not, and vice versa.
+     *
+     * <p>Used to represent a {@code NOT} boolean operation in a chain of validators.
+     *
+     * @throws IllegalArgumentException if {@code validator} is an instance of a class that is not
+     * provided by the Android System.
+     */
+    @NonNull
+    public static Validator not(@NonNull Validator validator) {
+        Preconditions.checkArgument(validator instanceof InternalValidator,
+                "validator not provided by Android System: " + validator);
+        return new NegationValidator((InternalValidator) validator);
+    }
+
+    private static InternalValidator[] getInternalValidators(Validator[] validators) {
+        Preconditions.checkArrayElementsNotNull(validators, "validators");
+
+        final InternalValidator[] internals = new InternalValidator[validators.length];
+
+        for (int i = 0; i < validators.length; i++) {
+            Preconditions.checkArgument((validators[i] instanceof InternalValidator),
+                    "element " + i + " not provided by Android System: " + validators[i]);
+            internals[i] = (InternalValidator) validators[i];
+        }
+        return internals;
+    }
+}
diff --git a/android/service/autofill/ValueFinder.java b/android/service/autofill/ValueFinder.java
new file mode 100644
index 0000000..7f195d6
--- /dev/null
+++ b/android/service/autofill/ValueFinder.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+/**
+ * Helper object used to obtain the value of a field in the screen being autofilled.
+ *
+ * @hide
+ */
+@TestApi
+public interface ValueFinder {
+
+    /**
+     * Gets the value of a field as String, or {@code null} when not found.
+     */
+    @Nullable
+    default String findByAutofillId(@NonNull AutofillId id) {
+        final AutofillValue value = findRawValueByAutofillId(id);
+        return (value == null || !value.isText()) ? null : value.getTextValue().toString();
+    }
+
+    /**
+     * Gets the value of a field, or {@code null} when not found.
+     */
+    @Nullable
+    AutofillValue findRawValueByAutofillId(@NonNull AutofillId id);
+}
diff --git a/android/service/autofill/VisibilitySetterAction.java b/android/service/autofill/VisibilitySetterAction.java
new file mode 100644
index 0000000..e29a23f
--- /dev/null
+++ b/android/service/autofill/VisibilitySetterAction.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+import static android.view.autofill.Helper.sVerbose;
+
+import android.annotation.IdRes;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.View.Visibility;
+import android.view.ViewGroup;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Action used to change the visibility of other child view in a {@link CustomDescription}
+ * {@link RemoteViews presentation template}.
+ *
+ * <p>See {@link CustomDescription.Builder#addOnClickAction(int, OnClickAction)} for more details.
+ */
+public final class VisibilitySetterAction extends InternalOnClickAction implements
+        OnClickAction, Parcelable {
+    private static final String TAG = "VisibilitySetterAction";
+
+    @NonNull private final SparseIntArray mVisibilities;
+
+    private VisibilitySetterAction(@NonNull Builder builder) {
+        mVisibilities = builder.mVisibilities;
+    }
+
+    /** @hide */
+    @Override
+    public void onClick(@NonNull ViewGroup rootView) {
+        for (int i = 0; i < mVisibilities.size(); i++) {
+            final int id = mVisibilities.keyAt(i);
+            final View child = rootView.findViewById(id);
+            if (child == null) {
+                Slog.w(TAG, "Skipping view id " + id + " because it's not found on " + rootView);
+                continue;
+            }
+            final int visibility = mVisibilities.valueAt(i);
+            if (sVerbose) {
+                Slog.v(TAG, "Changing visibility of view " + child + " from "
+                        + child.getVisibility() + " to  " + visibility);
+            }
+            child.setVisibility(visibility);
+        }
+    }
+
+    /**
+     * Builder for {@link VisibilitySetterAction} objects.
+     */
+    public static final class Builder {
+        private final SparseIntArray mVisibilities = new SparseIntArray();
+        private boolean mDestroyed;
+
+        /**
+         * Creates a new builder for an action that change the visibility of one child view.
+         *
+         * @param id view resource id of the children view.
+         * @param visibility one of {@link View#VISIBLE}, {@link View#INVISIBLE}, or
+         *            {@link View#GONE}.
+         * @throws IllegalArgumentException if visibility is not one of {@link View#VISIBLE},
+         * {@link View#INVISIBLE}, or {@link View#GONE}.
+         */
+        public Builder(@IdRes int id, @Visibility int visibility) {
+            setVisibility(id, visibility);
+        }
+
+        /**
+         * Sets the action to changes the visibility of a child view.
+         *
+         * @param id view resource id of the children view.
+         * @param visibility one of {@link View#VISIBLE}, {@link View#INVISIBLE}, or
+         *            {@link View#GONE}.
+         * @throws IllegalArgumentException if visibility is not one of {@link View#VISIBLE},
+         * {@link View#INVISIBLE}, or {@link View#GONE}.
+         */
+        @NonNull
+        public Builder setVisibility(@IdRes int id, @Visibility int visibility) {
+            throwIfDestroyed();
+            switch (visibility) {
+                case View.VISIBLE:
+                case View.INVISIBLE:
+                case View.GONE:
+                    mVisibilities.put(id, visibility);
+                    return this;
+            }
+            throw new IllegalArgumentException("Invalid visibility: " + visibility);
+        }
+
+        /**
+         * Creates a new {@link VisibilitySetterAction} instance.
+         */
+        @NonNull
+        public VisibilitySetterAction build() {
+            throwIfDestroyed();
+            mDestroyed = true;
+            return new VisibilitySetterAction(this);
+        }
+
+        private void throwIfDestroyed() {
+            Preconditions.checkState(!mDestroyed, "Already called build()");
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "VisibilitySetterAction: [" + mVisibilities + "]";
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeSparseIntArray(mVisibilities);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<VisibilitySetterAction> CREATOR =
+            new Parcelable.Creator<VisibilitySetterAction>() {
+
+        @NonNull
+        @Override
+        public VisibilitySetterAction 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
+            final SparseIntArray visibilities = parcel.readSparseIntArray();
+            Builder builder = null;
+            for (int i = 0; i < visibilities.size(); i++) {
+                final int id = visibilities.keyAt(i);
+                final int visibility = visibilities.valueAt(i);
+                if (builder == null) {
+                    builder = new Builder(id, visibility);
+                } else {
+                    builder.setVisibility(id, visibility);
+                }
+            }
+            return builder == null ? null : builder.build();
+        }
+
+        @NonNull
+        @Override
+        public VisibilitySetterAction[] newArray(int size) {
+            return new VisibilitySetterAction[size];
+        }
+    };
+}
diff --git a/android/service/autofill/augmented/AugmentedAutofillService.java b/android/service/autofill/augmented/AugmentedAutofillService.java
new file mode 100644
index 0000000..cca45f5
--- /dev/null
+++ b/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -0,0 +1,677 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill.augmented;
+
+import static android.service.autofill.augmented.Helper.logResponse;
+import static android.util.TimeUtils.formatDuration;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.BaseBundle;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.augmented.PresentationParams.SystemPopupPresentationParams;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
+import android.view.autofill.IAugmentedAutofillManagerClient;
+import android.view.autofill.IAutofillWindowPresenter;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A service used to augment the Autofill subsystem by potentially providing autofill data when the
+ * "standard" workflow failed (for example, because the standard AutofillService didn't have data).
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public abstract class AugmentedAutofillService extends Service {
+
+    private static final String TAG = AugmentedAutofillService.class.getSimpleName();
+
+    static boolean sDebug = Build.IS_USER ? false : true;
+    static boolean sVerbose = false;
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     * To be supported, the service must also require the
+     * {@link android.Manifest.permission#BIND_AUGMENTED_AUTOFILL_SERVICE} permission so
+     * that other applications can not abuse it.
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.service.autofill.augmented.AugmentedAutofillService";
+
+    private Handler mHandler;
+
+    private SparseArray<AutofillProxy> mAutofillProxies;
+
+    private AutofillProxy mAutofillProxyForLastRequest;
+
+    // Used for metrics / debug only
+    private ComponentName mServiceComponentName;
+
+    private final class AugmentedAutofillServiceImpl extends IAugmentedAutofillService.Stub {
+
+        @Override
+        public void onConnected(boolean debug, boolean verbose) {
+            mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnConnected,
+                    AugmentedAutofillService.this, debug, verbose));
+        }
+
+        @Override
+        public void onDisconnected() {
+            mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnDisconnected,
+                    AugmentedAutofillService.this));
+        }
+
+        @Override
+        public void onFillRequest(int sessionId, IBinder client, int taskId,
+                ComponentName componentName, AutofillId focusedId, AutofillValue focusedValue,
+                long requestTime, @Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
+                IFillCallback callback) {
+            mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnFillRequest,
+                    AugmentedAutofillService.this, sessionId, client, taskId, componentName,
+                    focusedId, focusedValue, requestTime, inlineSuggestionsRequest, callback));
+        }
+
+        @Override
+        public void onDestroyAllFillWindowsRequest() {
+            mHandler.sendMessage(
+                    obtainMessage(AugmentedAutofillService::handleOnDestroyAllFillWindowsRequest,
+                            AugmentedAutofillService.this));
+        }
+    };
+
+    @CallSuper
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mHandler = new Handler(Looper.getMainLooper(), null, true);
+        BaseBundle.setShouldDefuse(true);
+    }
+
+    /** @hide */
+    @Override
+    public final IBinder onBind(Intent intent) {
+        mServiceComponentName = intent.getComponent();
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return new AugmentedAutofillServiceImpl();
+        }
+        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+        return null;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnUnbind,
+                AugmentedAutofillService.this));
+        return false;
+    }
+
+    /**
+     * Called when the Android system connects to service.
+     *
+     * <p>You should generally do initialization here rather than in {@link #onCreate}.
+     */
+    public void onConnected() {
+    }
+
+    /**
+     * The child class of the service can call this method to initiate an Autofill flow.
+     *
+     * <p> The request would be respected only if the previous augmented autofill request was
+     * made for the same {@code activityComponent} and {@code autofillId}, and the field is
+     * currently on focus.
+     *
+     * <p> The request would start a new autofill flow. It doesn't guarantee that the
+     * {@link AutofillManager} will proceed with the request.
+     *
+     * @param activityComponent the client component for which the autofill is requested for
+     * @param autofillId        the client field id for which the autofill is requested for
+     * @return true if the request makes the {@link AutofillManager} start a new Autofill flow,
+     * false otherwise.
+     */
+    public final boolean requestAutofill(@NonNull ComponentName activityComponent,
+            @NonNull AutofillId autofillId) {
+        // TODO(b/149531989): revisit this. The request should start a new autofill session
+        //  rather than reusing the existing session.
+        final AutofillProxy proxy = mAutofillProxyForLastRequest;
+        if (proxy == null || !proxy.mComponentName.equals(activityComponent)
+                || !proxy.mFocusedId.equals(autofillId)) {
+            return false;
+        }
+        try {
+            return proxy.requestAutofill();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return false;
+    }
+
+    /**
+     * Asks the service to handle an "augmented" autofill request.
+     *
+     * <p>This method is called when the "stantard" autofill service cannot handle a request, which
+     * typically occurs when:
+     * <ul>
+     *   <li>Service does not recognize what should be autofilled.
+     *   <li>Service does not have data to fill the request.
+     *   <li>Service blacklisted that app (or activity) for autofill.
+     *   <li>App disabled itself for autofill.
+     * </ul>
+     *
+     * <p>Differently from the standard autofill workflow, on augmented autofill the service is
+     * responsible to generate the autofill UI and request the Android system to autofill the
+     * activity when the user taps an action in that UI (through the
+     * {@link FillController#autofill(List)} method).
+     *
+     * <p>The service <b>MUST</b> call {@link
+     * FillCallback#onSuccess(android.service.autofill.augmented.FillResponse)} as soon as possible,
+     * passing {@code null} when it cannot fulfill the request.
+     * @param request the request to handle.
+     * @param cancellationSignal signal for observing cancellation requests. The system will use
+     *     this to notify you that the fill result is no longer needed and you should stop
+     *     handling this fill request in order to save resources.
+     * @param controller object used to interact with the autofill system.
+     * @param callback object used to notify the result of the request. Service <b>must</b> call
+     * {@link FillCallback#onSuccess(android.service.autofill.augmented.FillResponse)}.
+     */
+    public void onFillRequest(@NonNull FillRequest request,
+            @NonNull CancellationSignal cancellationSignal, @NonNull FillController controller,
+            @NonNull FillCallback callback) {
+    }
+
+    /**
+     * Called when the Android system disconnects from the service.
+     *
+     * <p> At this point this service may no longer be an active {@link AugmentedAutofillService}.
+     * It should not make calls on {@link AutofillManager} that requires the caller to be
+     * the current service.
+     */
+    public void onDisconnected() {
+    }
+
+    private void handleOnConnected(boolean debug, boolean verbose) {
+        if (sDebug || debug) {
+            Log.d(TAG, "handleOnConnected(): debug=" + debug + ", verbose=" + verbose);
+        }
+        sDebug = debug;
+        sVerbose = verbose;
+        onConnected();
+    }
+
+    private void handleOnDisconnected() {
+        onDisconnected();
+    }
+
+    private void handleOnFillRequest(int sessionId, @NonNull IBinder client, int taskId,
+            @NonNull ComponentName componentName, @NonNull AutofillId focusedId,
+            @Nullable AutofillValue focusedValue, long requestTime,
+            @Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
+            @NonNull IFillCallback callback) {
+        if (mAutofillProxies == null) {
+            mAutofillProxies = new SparseArray<>();
+        }
+
+        final ICancellationSignal transport = CancellationSignal.createTransport();
+        final CancellationSignal cancellationSignal = CancellationSignal.fromTransport(transport);
+        AutofillProxy proxy = mAutofillProxies.get(sessionId);
+        if (proxy == null) {
+            proxy = new AutofillProxy(sessionId, client, taskId, mServiceComponentName,
+                    componentName, focusedId, focusedValue, requestTime, callback,
+                    cancellationSignal);
+            mAutofillProxies.put(sessionId,  proxy);
+        } else {
+            // TODO(b/123099468): figure out if it's ok to reuse the proxy; add logging
+            if (sDebug) Log.d(TAG, "Reusing proxy for session " + sessionId);
+            proxy.update(focusedId, focusedValue, callback, cancellationSignal);
+        }
+
+        try {
+            callback.onCancellable(transport);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        mAutofillProxyForLastRequest = proxy;
+        onFillRequest(new FillRequest(proxy, inlineSuggestionsRequest), cancellationSignal,
+                new FillController(proxy), new FillCallback(proxy));
+    }
+
+    private void handleOnDestroyAllFillWindowsRequest() {
+        if (mAutofillProxies != null) {
+            final int size = mAutofillProxies.size();
+            for (int i = 0; i < size; i++) {
+                final int sessionId = mAutofillProxies.keyAt(i);
+                final AutofillProxy proxy = mAutofillProxies.valueAt(i);
+                if (proxy == null) {
+                    // TODO(b/123100811): this might be fine, in which case we should logv it
+                    Log.w(TAG, "No proxy for session " + sessionId);
+                    return;
+                }
+                if (proxy.mCallback != null) {
+                    try {
+                        if (!proxy.mCallback.isCompleted()) {
+                            proxy.mCallback.cancel();
+                        }
+                    } catch (Exception e) {
+                        Log.e(TAG, "failed to check current pending request status", e);
+                    }
+                }
+                proxy.destroy();
+            }
+            mAutofillProxies.clear();
+            mAutofillProxyForLastRequest = null;
+        }
+    }
+
+    private void handleOnUnbind() {
+        if (mAutofillProxies == null) {
+            if (sDebug) Log.d(TAG, "onUnbind(): no proxy to destroy");
+            return;
+        }
+        final int size = mAutofillProxies.size();
+        if (sDebug) Log.d(TAG, "onUnbind(): destroying " + size + " proxies");
+        for (int i = 0; i < size; i++) {
+            final AutofillProxy proxy = mAutofillProxies.valueAt(i);
+            try {
+                proxy.destroy();
+            } catch (Exception e) {
+                Log.w(TAG, "error destroying " + proxy);
+            }
+        }
+        mAutofillProxies = null;
+        mAutofillProxyForLastRequest = null;
+    }
+
+    @Override
+    /** @hide */
+    protected final void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.print("Service component: "); pw.println(
+                ComponentName.flattenToShortString(mServiceComponentName));
+        if (mAutofillProxies != null) {
+            final int size = mAutofillProxies.size();
+            pw.print("Number proxies: "); pw.println(size);
+            for (int i = 0; i < size; i++) {
+                final int sessionId = mAutofillProxies.keyAt(i);
+                final AutofillProxy proxy = mAutofillProxies.valueAt(i);
+                pw.print(i); pw.print(") SessionId="); pw.print(sessionId); pw.println(":");
+                proxy.dump("  ", pw);
+            }
+        }
+        dump(pw, args);
+    }
+
+    /**
+     * Implementation specific {@code dump}. The child class can override the method to provide
+     * additional information about the Service's state into the dumpsys output.
+     *
+     * @param pw The PrintWriter to which you should dump your state.  This will be closed for
+     * you after you return.
+     * @param args additional arguments to the dump request.
+     */
+    protected void dump(@NonNull PrintWriter pw,
+            @SuppressWarnings("unused") @NonNull String[] args) {
+        pw.print(getClass().getName()); pw.println(": nothing to dump");
+    }
+
+    /**
+     * Gets the inline augmented autofill events that happened after the last
+     * {@link #onFillRequest(FillRequest, CancellationSignal, FillController, FillCallback)} call.
+     *
+     * <p>The history is not persisted over reboots, and it's cleared every time the service
+     * replies to a
+     * {@link #onFillRequest(FillRequest, CancellationSignal, FillController, FillCallback)}
+     * by calling {@link FillCallback#onSuccess(FillResponse)}. Hence, the service should call
+     * {@link #getFillEventHistory() before finishing the {@link FillCallback}.
+     *
+     * <p>Also note that the events from the dropdown suggestion UI is not stored in the history
+     * since the service owns the UI.
+     *
+     * @return The history or {@code null} if there are no events.
+     */
+    @Nullable public final FillEventHistory getFillEventHistory() {
+        final AutofillManager afm = getSystemService(AutofillManager.class);
+
+        if (afm == null) {
+            return null;
+        } else {
+            return afm.getFillEventHistory();
+        }
+    }
+
+    /** @hide */
+    static final class AutofillProxy {
+
+        static final int REPORT_EVENT_NO_RESPONSE = 1;
+        static final int REPORT_EVENT_UI_SHOWN = 2;
+        static final int REPORT_EVENT_UI_DESTROYED = 3;
+        static final int REPORT_EVENT_INLINE_RESPONSE = 4;
+
+        @IntDef(prefix = { "REPORT_EVENT_" }, value = {
+                REPORT_EVENT_NO_RESPONSE,
+                REPORT_EVENT_UI_SHOWN,
+                REPORT_EVENT_UI_DESTROYED,
+                REPORT_EVENT_INLINE_RESPONSE
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface ReportEvent{}
+
+
+        private final Object mLock = new Object();
+        private final IAugmentedAutofillManagerClient mClient;
+        private final int mSessionId;
+        public final int mTaskId;
+        public final ComponentName mComponentName;
+        // Used for metrics / debug only
+        private String mServicePackageName;
+        @GuardedBy("mLock")
+        private AutofillId mFocusedId;
+        @GuardedBy("mLock")
+        private AutofillValue mFocusedValue;
+        @GuardedBy("mLock")
+        private IFillCallback mCallback;
+
+        /**
+         * Id of the last field that cause the Autofill UI to be shown.
+         *
+         * <p>Used to make sure the SmartSuggestionsParams is updated when a new fields is focused.
+         */
+        @GuardedBy("mLock")
+        private AutofillId mLastShownId;
+
+        // Objects used to log metrics
+        private final long mFirstRequestTime;
+        private long mFirstOnSuccessTime;
+        private long mUiFirstShownTime;
+        private long mUiFirstDestroyedTime;
+
+        @GuardedBy("mLock")
+        private SystemPopupPresentationParams mSmartSuggestion;
+
+        @GuardedBy("mLock")
+        private FillWindow mFillWindow;
+
+        private CancellationSignal mCancellationSignal;
+
+        private AutofillProxy(int sessionId, @NonNull IBinder client, int taskId,
+                @NonNull ComponentName serviceComponentName,
+                @NonNull ComponentName componentName, @NonNull AutofillId focusedId,
+                @Nullable AutofillValue focusedValue, long requestTime,
+                @NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal) {
+            mSessionId = sessionId;
+            mClient = IAugmentedAutofillManagerClient.Stub.asInterface(client);
+            mCallback = callback;
+            mTaskId = taskId;
+            mComponentName = componentName;
+            mServicePackageName = serviceComponentName.getPackageName();
+            mFocusedId = focusedId;
+            mFocusedValue = focusedValue;
+            mFirstRequestTime = requestTime;
+            mCancellationSignal = cancellationSignal;
+            // TODO(b/123099468): linkToDeath
+        }
+
+        @NonNull
+        public SystemPopupPresentationParams getSmartSuggestionParams() {
+            synchronized (mLock) {
+                if (mSmartSuggestion != null && mFocusedId.equals(mLastShownId)) {
+                    return mSmartSuggestion;
+                }
+                Rect rect;
+                try {
+                    rect = mClient.getViewCoordinates(mFocusedId);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Could not get coordinates for " + mFocusedId);
+                    return null;
+                }
+                if (rect == null) {
+                    if (sDebug) Log.d(TAG, "getViewCoordinates(" + mFocusedId + ") returned null");
+                    return null;
+                }
+                mSmartSuggestion = new SystemPopupPresentationParams(this, rect);
+                mLastShownId = mFocusedId;
+                return mSmartSuggestion;
+            }
+        }
+
+        public void autofill(@NonNull List<Pair<AutofillId, AutofillValue>> pairs)
+                throws RemoteException {
+            final int size = pairs.size();
+            final List<AutofillId> ids = new ArrayList<>(size);
+            final List<AutofillValue> values = new ArrayList<>(size);
+            for (int i = 0; i < size; i++) {
+                final Pair<AutofillId, AutofillValue> pair = pairs.get(i);
+                ids.add(pair.first);
+                values.add(pair.second);
+            }
+            final boolean hideHighlight = size == 1 && ids.get(0).equals(mFocusedId);
+            mClient.autofill(mSessionId, ids, values, hideHighlight);
+        }
+
+        public void setFillWindow(@NonNull FillWindow fillWindow) {
+            synchronized (mLock) {
+                mFillWindow = fillWindow;
+            }
+        }
+
+        public FillWindow getFillWindow() {
+            synchronized (mLock) {
+                return mFillWindow;
+            }
+        }
+
+        public void requestShowFillUi(int width, int height, Rect anchorBounds,
+                IAutofillWindowPresenter presenter) throws RemoteException {
+            if (mCancellationSignal.isCanceled()) {
+                if (sVerbose) {
+                    Log.v(TAG, "requestShowFillUi() not showing because request is cancelled");
+                }
+                return;
+            }
+            mClient.requestShowFillUi(mSessionId, mFocusedId, width, height, anchorBounds,
+                    presenter);
+        }
+
+        public void requestHideFillUi() throws RemoteException {
+            mClient.requestHideFillUi(mSessionId, mFocusedId);
+        }
+
+
+        private boolean requestAutofill() throws RemoteException {
+            return mClient.requestAutofill(mSessionId, mFocusedId);
+        }
+
+        private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue,
+                @NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal) {
+            synchronized (mLock) {
+                mFocusedId = focusedId;
+                mFocusedValue = focusedValue;
+                if (mCallback != null) {
+                    try {
+                        if (!mCallback.isCompleted()) {
+                            mCallback.cancel();
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "failed to check current pending request status", e);
+                    }
+                    Log.d(TAG, "mCallback is updated.");
+                }
+                mCallback = callback;
+                mCancellationSignal = cancellationSignal;
+            }
+        }
+
+        @NonNull
+        public AutofillId getFocusedId() {
+            synchronized (mLock) {
+                return mFocusedId;
+            }
+        }
+
+        @NonNull
+        public AutofillValue getFocusedValue() {
+            synchronized (mLock) {
+                return mFocusedValue;
+            }
+        }
+
+        void reportResult(@Nullable List<Dataset> inlineSuggestionsData,
+                @Nullable Bundle clientState) {
+            try {
+                mCallback.onSuccess(inlineSuggestionsData, clientState);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling back with the inline suggestions data: " + e);
+            }
+        }
+
+        void logEvent(@ReportEvent int event) {
+            if (sVerbose) Log.v(TAG, "returnAndLogResult(): " + event);
+            long duration = -1;
+            int type = MetricsEvent.TYPE_UNKNOWN;
+
+            switch (event) {
+                case REPORT_EVENT_NO_RESPONSE: {
+                    type = MetricsEvent.TYPE_SUCCESS;
+                    if (mFirstOnSuccessTime == 0) {
+                        mFirstOnSuccessTime = SystemClock.elapsedRealtime();
+                        duration = mFirstOnSuccessTime - mFirstRequestTime;
+                        if (sDebug) {
+                            Log.d(TAG, "Service responded nothing in " + formatDuration(duration));
+                        }
+                    }
+                } break;
+
+                case REPORT_EVENT_INLINE_RESPONSE: {
+                    // TODO: Define a constant and log this event
+                    // type = MetricsEvent.TYPE_SUCCESS_INLINE;
+                    if (mFirstOnSuccessTime == 0) {
+                        mFirstOnSuccessTime = SystemClock.elapsedRealtime();
+                        duration = mFirstOnSuccessTime - mFirstRequestTime;
+                        if (sDebug) {
+                            Log.d(TAG, "Service responded nothing in " + formatDuration(duration));
+                        }
+                    }
+                } break;
+
+                case REPORT_EVENT_UI_SHOWN: {
+                    type = MetricsEvent.TYPE_OPEN;
+                    if (mUiFirstShownTime == 0) {
+                        mUiFirstShownTime = SystemClock.elapsedRealtime();
+                        duration = mUiFirstShownTime - mFirstRequestTime;
+                        if (sDebug) Log.d(TAG, "UI shown in " + formatDuration(duration));
+                    }
+                } break;
+
+                case REPORT_EVENT_UI_DESTROYED: {
+                    type = MetricsEvent.TYPE_CLOSE;
+                    if (mUiFirstDestroyedTime == 0) {
+                        mUiFirstDestroyedTime = SystemClock.elapsedRealtime();
+                        duration = mUiFirstDestroyedTime - mFirstRequestTime;
+                        if (sDebug) Log.d(TAG, "UI destroyed in " + formatDuration(duration));
+                    }
+                } break;
+
+                default:
+                    Log.w(TAG, "invalid event reported: " + event);
+            }
+            logResponse(type, mServicePackageName, mComponentName, mSessionId, duration);
+        }
+
+        public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+            pw.print(prefix); pw.print("sessionId: "); pw.println(mSessionId);
+            pw.print(prefix); pw.print("taskId: "); pw.println(mTaskId);
+            pw.print(prefix); pw.print("component: ");
+            pw.println(mComponentName.flattenToShortString());
+            pw.print(prefix); pw.print("focusedId: "); pw.println(mFocusedId);
+            if (mFocusedValue != null) {
+                pw.print(prefix); pw.print("focusedValue: "); pw.println(mFocusedValue);
+            }
+            if (mLastShownId != null) {
+                pw.print(prefix); pw.print("lastShownId: "); pw.println(mLastShownId);
+            }
+            pw.print(prefix); pw.print("client: "); pw.println(mClient);
+            final String prefix2 = prefix + "  ";
+            if (mFillWindow != null) {
+                pw.print(prefix); pw.println("window:");
+                mFillWindow.dump(prefix2, pw);
+            }
+            if (mSmartSuggestion != null) {
+                pw.print(prefix); pw.println("smartSuggestion:");
+                mSmartSuggestion.dump(prefix2, pw);
+            }
+            if (mFirstOnSuccessTime > 0) {
+                final long responseTime = mFirstOnSuccessTime - mFirstRequestTime;
+                pw.print(prefix); pw.print("response time: ");
+                formatDuration(responseTime, pw); pw.println();
+            }
+
+            if (mUiFirstShownTime > 0) {
+                final long uiRenderingTime = mUiFirstShownTime - mFirstRequestTime;
+                pw.print(prefix); pw.print("UI rendering time: ");
+                formatDuration(uiRenderingTime, pw); pw.println();
+            }
+
+            if (mUiFirstDestroyedTime > 0) {
+                final long uiTotalTime = mUiFirstDestroyedTime - mFirstRequestTime;
+                pw.print(prefix); pw.print("UI life time: ");
+                formatDuration(uiTotalTime, pw); pw.println();
+            }
+        }
+
+        private void destroy() {
+            synchronized (mLock) {
+                if (mFillWindow != null) {
+                    if (sDebug) Log.d(TAG, "destroying window");
+                    mFillWindow.destroy();
+                    mFillWindow = null;
+                }
+            }
+        }
+    }
+}
diff --git a/android/service/autofill/augmented/FillCallback.java b/android/service/autofill/augmented/FillCallback.java
new file mode 100644
index 0000000..21738d8
--- /dev/null
+++ b/android/service/autofill/augmented/FillCallback.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill.augmented;
+
+import static android.service.autofill.augmented.AugmentedAutofillService.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Bundle;
+import android.service.autofill.Dataset;
+import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Callback used to indicate at {@link FillRequest} has been fulfilled.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class FillCallback {
+
+    private static final String TAG = FillCallback.class.getSimpleName();
+
+    private final AutofillProxy mProxy;
+
+    FillCallback(@NonNull AutofillProxy proxy) {
+        mProxy = proxy;
+    }
+
+    /**
+     * Sets the response associated with the request.
+     *
+     * @param response response associated with the request, or {@code null} if the service
+     *                 could not provide autofill for the request.
+     */
+    public void onSuccess(@Nullable FillResponse response) {
+        if (sDebug) Log.d(TAG, "onSuccess(): " + response);
+
+        if (response == null) {
+            mProxy.logEvent(AutofillProxy.REPORT_EVENT_NO_RESPONSE);
+            mProxy.reportResult(/* inlineSuggestionsData */ null, /* clientState */ null);
+            return;
+        }
+
+        List<Dataset> inlineSuggestions = response.getInlineSuggestions();
+        Bundle clientState = response.getClientState();
+        if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) {
+            mProxy.logEvent(AutofillProxy.REPORT_EVENT_INLINE_RESPONSE);
+            mProxy.reportResult(inlineSuggestions, clientState);
+            return;
+        }
+
+        final FillWindow fillWindow = response.getFillWindow();
+        if (fillWindow != null) {
+            fillWindow.show();
+        }
+        // TODO(b/123099468): must notify the server so it can update the session state to avoid
+        // showing conflicting UIs (for example, if a new request is made to the main autofill
+        // service and it now wants to show something).
+    }
+}
diff --git a/android/service/autofill/augmented/FillController.java b/android/service/autofill/augmented/FillController.java
new file mode 100644
index 0000000..7d552d6
--- /dev/null
+++ b/android/service/autofill/augmented/FillController.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill.augmented;
+
+import static android.service.autofill.augmented.AugmentedAutofillService.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.RemoteException;
+import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
+import android.util.Log;
+import android.util.Pair;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Object used to interact with the autofill system.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class FillController {
+    private static final String TAG = FillController.class.getSimpleName();
+
+    private final AutofillProxy mProxy;
+
+    FillController(@NonNull AutofillProxy proxy) {
+        mProxy = proxy;
+    }
+
+    /**
+     * Fills the activity with the provided values.
+     *
+     * <p>As a side effect, the {@link FillWindow} associated with the {@link FillResponse} will be
+     * automatically {@link FillWindow#destroy() destroyed}.
+     */
+    public void autofill(@NonNull List<Pair<AutofillId, AutofillValue>> values) {
+        Preconditions.checkNotNull(values);
+
+        if (sDebug) {
+            Log.d(TAG, "autofill() with " + values.size() + " values");
+        }
+
+        try {
+            mProxy.autofill(values);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+
+        final FillWindow fillWindow = mProxy.getFillWindow();
+        if (fillWindow != null) {
+            fillWindow.destroy();
+        }
+    }
+}
diff --git a/android/service/autofill/augmented/FillRequest.java b/android/service/autofill/augmented/FillRequest.java
new file mode 100644
index 0000000..6927cf6
--- /dev/null
+++ b/android/service/autofill/augmented/FillRequest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill.augmented;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.ComponentName;
+import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Represents a request to augment-fill an activity.
+ * @hide
+ */
+@SystemApi
+// TODO(b/123100811): pass a requestId and/or sessionId?
+@TestApi
+@DataClass(
+        genToString = true,
+        genBuilder = false,
+        genHiddenConstructor = true)
[email protected]({"getProxy"})
+public final class FillRequest {
+
+    private final @NonNull AutofillProxy mProxy;
+
+    //TODO(b/146901891): add detailed docs once we have stable APIs.
+    /**
+     * An optional request for inline suggestions.
+     */
+    private final @Nullable InlineSuggestionsRequest mInlineSuggestionsRequest;
+
+    /**
+     * Gets the task of the activity associated with this request.
+     */
+    public int getTaskId() {
+        return mProxy.mTaskId;
+    }
+
+    /**
+     * Gets the name of the activity associated with this request.
+     */
+    @NonNull
+    public ComponentName getActivityComponent() {
+        return mProxy.mComponentName;
+    }
+
+    /**
+     * Gets the id of the field that triggered the request.
+     */
+    @NonNull
+    public AutofillId getFocusedId() {
+        return mProxy.getFocusedId();
+    }
+
+    /**
+     * Gets the current value of the field that triggered the request.
+     */
+    @NonNull
+    public AutofillValue getFocusedValue() {
+        return mProxy.getFocusedValue();
+    }
+
+    /**
+     * Gets the Smart Suggestions object used to embed the autofill UI.
+     *
+     * @return object used to embed the autofill UI, or {@code null} if not supported.
+     */
+    @Nullable
+    public PresentationParams getPresentationParams() {
+        return mProxy.getSmartSuggestionParams();
+    }
+
+    String proxyToString() {
+        return "FillRequest[act=" + getActivityComponent().flattenToShortString()
+                + ", id=" + mProxy.getFocusedId() + "]";
+    }
+
+
+
+
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/augmented/FillRequest.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new FillRequest.
+     *
+     * @param inlineSuggestionsRequest
+     *   An optional request for inline suggestions.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public FillRequest(
+            @NonNull AutofillProxy proxy,
+            @Nullable InlineSuggestionsRequest inlineSuggestionsRequest) {
+        this.mProxy = proxy;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mProxy);
+        this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * An optional request for inline suggestions.
+     */
+    @DataClass.Generated.Member
+    public @Nullable InlineSuggestionsRequest getInlineSuggestionsRequest() {
+        return mInlineSuggestionsRequest;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "FillRequest { " +
+                "proxy = " + proxyToString() + ", " +
+                "inlineSuggestionsRequest = " + mInlineSuggestionsRequest +
+        " }";
+    }
+
+    @DataClass.Generated(
+            time = 1577399314707L,
+            codegenVersion = "1.0.14",
+            sourceFile = "frameworks/base/core/java/android/service/autofill/augmented/FillRequest.java",
+            inputSignatures = "private final @android.annotation.NonNull android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy mProxy\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\npublic  int getTaskId()\npublic @android.annotation.NonNull android.content.ComponentName getActivityComponent()\npublic @android.annotation.NonNull android.view.autofill.AutofillId getFocusedId()\npublic @android.annotation.NonNull android.view.autofill.AutofillValue getFocusedValue()\npublic @android.annotation.Nullable android.service.autofill.augmented.PresentationParams getPresentationParams()\n  java.lang.String proxyToString()\nclass FillRequest extends java.lang.Object implements []\[email protected](genToString=true, genBuilder=false, genHiddenConstructor=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/service/autofill/augmented/FillResponse.java b/android/service/autofill/augmented/FillResponse.java
new file mode 100644
index 0000000..f72eb78
--- /dev/null
+++ b/android/service/autofill/augmented/FillResponse.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill.augmented;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Bundle;
+import android.service.autofill.Dataset;
+
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Response to a {@link FillRequest}.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+@DataClass(
+        genBuilder = true,
+        genHiddenGetters = true)
+public final class FillResponse {
+
+    /**
+     * The {@link FillWindow} used to display the Autofill UI.
+     */
+    private @Nullable FillWindow mFillWindow;
+
+    /**
+     * The {@link Dataset}s representing the inline suggestions data. Defaults to null if no
+     * inline suggestions are available from the service.
+     */
+    @DataClass.PluralOf("inlineSuggestion")
+    private @Nullable List<Dataset> mInlineSuggestions;
+
+    /**
+     * The client state that {@link AugmentedAutofillService} implementation can put anything in to
+     * identify the request and the response when calling
+     * {@link AugmentedAutofillService#getFillEventHistory()}.
+     */
+    private @Nullable Bundle mClientState;
+
+    private static FillWindow defaultFillWindow() {
+        return null;
+    }
+
+    private static List<Dataset> defaultInlineSuggestions() {
+        return null;
+    }
+
+    private static Bundle defaultClientState() {
+        return null;
+    }
+
+
+    /** @hide */
+    abstract static class BaseBuilder {
+        abstract FillResponse.Builder addInlineSuggestion(@NonNull Dataset value);
+    }
+
+
+
+    // Code below generated by codegen v1.0.15.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/augmented/FillResponse.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    /* package-private */ FillResponse(
+            @Nullable FillWindow fillWindow,
+            @Nullable List<Dataset> inlineSuggestions,
+            @Nullable Bundle clientState) {
+        this.mFillWindow = fillWindow;
+        this.mInlineSuggestions = inlineSuggestions;
+        this.mClientState = clientState;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The {@link FillWindow} used to display the Autofill UI.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @Nullable FillWindow getFillWindow() {
+        return mFillWindow;
+    }
+
+    /**
+     * The {@link Dataset}s representing the inline suggestions data. Defaults to null if no
+     * inline suggestions are available from the service.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @Nullable List<Dataset> getInlineSuggestions() {
+        return mInlineSuggestions;
+    }
+
+    /**
+     * The client state that {@link AugmentedAutofillService} implementation can put anything in to
+     * identify the request and the response when calling
+     * {@link AugmentedAutofillService#getFillEventHistory()}.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @Nullable Bundle getClientState() {
+        return mClientState;
+    }
+
+    /**
+     * A builder for {@link FillResponse}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder extends BaseBuilder {
+
+        private @Nullable FillWindow mFillWindow;
+        private @Nullable List<Dataset> mInlineSuggestions;
+        private @Nullable Bundle mClientState;
+
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder() {
+        }
+
+        /**
+         * The {@link FillWindow} used to display the Autofill UI.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setFillWindow(@NonNull FillWindow value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mFillWindow = value;
+            return this;
+        }
+
+        /**
+         * The {@link Dataset}s representing the inline suggestions data. Defaults to null if no
+         * inline suggestions are available from the service.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setInlineSuggestions(@NonNull List<Dataset> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mInlineSuggestions = value;
+            return this;
+        }
+
+        /** @see #setInlineSuggestions */
+        @DataClass.Generated.Member
+        @Override
+        @NonNull FillResponse.Builder addInlineSuggestion(@NonNull Dataset value) {
+            if (mInlineSuggestions == null) setInlineSuggestions(new ArrayList<>());
+            mInlineSuggestions.add(value);
+            return this;
+        }
+
+        /**
+         * The client state that {@link AugmentedAutofillService} implementation can put anything in to
+         * identify the request and the response when calling
+         * {@link AugmentedAutofillService#getFillEventHistory()}.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setClientState(@NonNull Bundle value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mClientState = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull FillResponse build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mFillWindow = defaultFillWindow();
+            }
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mInlineSuggestions = defaultInlineSuggestions();
+            }
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mClientState = defaultClientState();
+            }
+            FillResponse o = new FillResponse(
+                    mFillWindow,
+                    mInlineSuggestions,
+                    mClientState);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x8) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1584480900526L,
+            codegenVersion = "1.0.15",
+            sourceFile = "frameworks/base/core/java/android/service/autofill/augmented/FillResponse.java",
+            inputSignatures = "private @android.annotation.Nullable android.service.autofill.augmented.FillWindow mFillWindow\nprivate @com.android.internal.util.DataClass.PluralOf(\"inlineSuggestion\") @android.annotation.Nullable java.util.List<android.service.autofill.Dataset> mInlineSuggestions\nprivate @android.annotation.Nullable android.os.Bundle mClientState\nprivate static  android.service.autofill.augmented.FillWindow defaultFillWindow()\nprivate static  java.util.List<android.service.autofill.Dataset> defaultInlineSuggestions()\nprivate static  android.os.Bundle defaultClientState()\nclass FillResponse extends java.lang.Object implements []\[email protected](genBuilder=true, genHiddenGetters=true)\nabstract  android.service.autofill.augmented.FillResponse.Builder addInlineSuggestion(android.service.autofill.Dataset)\nclass BaseBuilder extends java.lang.Object implements []")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/android/service/autofill/augmented/FillWindow.java b/android/service/autofill/augmented/FillWindow.java
new file mode 100644
index 0000000..077df6c
--- /dev/null
+++ b/android/service/autofill/augmented/FillWindow.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill.augmented;
+
+import static android.service.autofill.augmented.AugmentedAutofillService.sDebug;
+import static android.service.autofill.augmented.AugmentedAutofillService.sVerbose;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
+import android.service.autofill.augmented.PresentationParams.Area;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.autofill.IAutofillWindowPresenter;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import dalvik.system.CloseGuard;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+
+/**
+ * Handle to a window used to display the augmented autofill UI.
+ *
+ * <p>The steps to create an augmented autofill UI are:
+ *
+ * <ol>
+ *   <li>Gets the {@link PresentationParams} from the {@link FillRequest}.
+ *   <li>Gets the {@link Area} to display the UI (for example, through
+ *   {@link PresentationParams#getSuggestionArea()}.
+ *   <li>Creates a {@link View} that must fit in the {@link Area#getBounds() area boundaries}.
+ *   <li>Set the proper listeners to the view (for example, a click listener that
+ *   triggers {@link FillController#autofill(java.util.List)}
+ *   <li>Call {@link #update(Area, View, long)} with these arguments.
+ *   <li>Create a {@link FillResponse} with the {@link FillWindow}.
+ *   <li>Pass such {@link FillResponse} to {@link FillCallback#onSuccess(FillResponse)}.
+ * </ol>
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class FillWindow implements AutoCloseable {
+    private static final String TAG = FillWindow.class.getSimpleName();
+
+    private final Object mLock = new Object();
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    private final @NonNull Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
+
+    @GuardedBy("mLock")
+    private @NonNull WindowManager mWm;
+    @GuardedBy("mLock")
+    private View mFillView;
+    @GuardedBy("mLock")
+    private boolean mShowing;
+    @GuardedBy("mLock")
+    private @Nullable Rect mBounds;
+
+    @GuardedBy("mLock")
+    private boolean mUpdateCalled;
+    @GuardedBy("mLock")
+    private boolean mDestroyed;
+
+    private @NonNull AutofillProxy mProxy;
+
+    /**
+     * Updates the content of the window.
+     *
+     * @param rootView new root view
+     * @param area coordinates to render the view.
+     * @param flags currently not used.
+     *
+     * @return boolean whether the window was updated or not.
+     *
+     * @throws IllegalArgumentException if the area is not compatible with this window
+     */
+    public boolean update(@NonNull Area area, @NonNull View rootView, long flags) {
+        if (sDebug) {
+            Log.d(TAG, "Updating " + area + " + with " + rootView);
+        }
+        // TODO(b/123100712): add test case for null
+        Preconditions.checkNotNull(area);
+        Preconditions.checkNotNull(area.proxy);
+        Preconditions.checkNotNull(rootView);
+        // TODO(b/123100712): must check the area is a valid object returned by
+        // SmartSuggestionParams, throw IAE if not
+
+        final PresentationParams smartSuggestion = area.proxy.getSmartSuggestionParams();
+        if (smartSuggestion == null) {
+            Log.w(TAG, "No SmartSuggestionParams");
+            return false;
+        }
+
+        final Rect rect = area.getBounds();
+        if (rect == null) {
+            Log.wtf(TAG, "No Rect on SmartSuggestionParams");
+            return false;
+        }
+
+        synchronized (mLock) {
+            checkNotDestroyedLocked();
+
+            mProxy = area.proxy;
+
+            // TODO(b/123227534): once we have the SurfaceControl approach, we should update the
+            // window instead of destroying. In fact, it might be better to allocate a full window
+            // initially, which is transparent (and let touches get through) everywhere but in the
+            // rect boundaries.
+
+            // TODO(b/123099468): make sure all touch events are handled, window is always closed,
+            // etc.
+
+            mWm = rootView.getContext().getSystemService(WindowManager.class);
+            mFillView = rootView;
+            // Listen to the touch outside to destroy the window when typing is detected.
+            mFillView.setOnTouchListener(
+                    (view, motionEvent) -> {
+                        if (motionEvent.getAction() == MotionEvent.ACTION_OUTSIDE) {
+                            if (sVerbose) Log.v(TAG, "Outside touch detected, hiding the window");
+                            hide();
+                        }
+                        return false;
+                    }
+            );
+            mShowing = false;
+            mBounds = new Rect(area.getBounds());
+            if (sDebug) {
+                Log.d(TAG, "Created FillWindow: params= " + smartSuggestion + " view=" + rootView);
+            }
+            mUpdateCalled = true;
+            mDestroyed = false;
+            mProxy.setFillWindow(this);
+            return true;
+        }
+    }
+
+    /** @hide */
+    void show() {
+        // TODO(b/123100712): check if updated first / throw exception
+        if (sDebug) Log.d(TAG, "show()");
+        synchronized (mLock) {
+            checkNotDestroyedLocked();
+            if (mWm == null || mFillView == null) {
+                throw new IllegalStateException("update() not called yet, or already destroyed()");
+            }
+            if (mProxy != null) {
+                try {
+                    mProxy.requestShowFillUi(mBounds.right - mBounds.left,
+                            mBounds.bottom - mBounds.top,
+                            /*anchorBounds=*/ null, new FillWindowPresenter(this));
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Error requesting to show fill window", e);
+                }
+                mProxy.logEvent(AutofillProxy.REPORT_EVENT_UI_SHOWN);
+            }
+        }
+    }
+
+    /**
+     * Hides the window.
+     *
+     * <p>The window is not destroyed and can be shown again
+     */
+    private void hide() {
+        if (sDebug) Log.d(TAG, "hide()");
+        synchronized (mLock) {
+            checkNotDestroyedLocked();
+            if (mWm == null || mFillView == null) {
+                throw new IllegalStateException("update() not called yet, or already destroyed()");
+            }
+            if (mProxy != null && mShowing) {
+                try {
+                    mProxy.requestHideFillUi();
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Error requesting to hide fill window", e);
+                }
+            }
+        }
+    }
+
+    private void handleShow(WindowManager.LayoutParams p) {
+        if (sDebug) Log.d(TAG, "handleShow()");
+        synchronized (mLock) {
+            if (mWm != null && mFillView != null) {
+                p.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+                if (!mShowing) {
+                    mWm.addView(mFillView, p);
+                    mShowing = true;
+                } else {
+                    mWm.updateViewLayout(mFillView, p);
+                }
+            }
+        }
+    }
+
+    private void handleHide() {
+        if (sDebug) Log.d(TAG, "handleHide()");
+        synchronized (mLock) {
+            if (mWm != null && mFillView != null && mShowing) {
+                mWm.removeView(mFillView);
+                mShowing = false;
+            }
+        }
+    }
+
+    /**
+     * Destroys the window.
+     *
+     * <p>Once destroyed, this window cannot be used anymore
+     */
+    public void destroy() {
+        if (sDebug) {
+            Log.d(TAG,
+                    "destroy(): mDestroyed=" + mDestroyed + " mShowing=" + mShowing + " mFillView="
+                            + mFillView);
+        }
+        synchronized (mLock) {
+            if (mDestroyed) return;
+            if (mUpdateCalled) {
+                mFillView.setOnClickListener(null);
+                hide();
+                mProxy.logEvent(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
+            }
+            mDestroyed = true;
+            mCloseGuard.close();
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            mCloseGuard.warnIfOpen();
+            destroy();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private void checkNotDestroyedLocked() {
+        if (mDestroyed) {
+            throw new IllegalStateException("already destroyed()");
+        }
+    }
+
+    /** @hide */
+    public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+        synchronized (this) {
+            pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
+            pw.print(prefix); pw.print("updateCalled: "); pw.println(mUpdateCalled);
+            if (mFillView != null) {
+                pw.print(prefix); pw.print("fill window: ");
+                pw.println(mShowing ? "shown" : "hidden");
+                pw.print(prefix); pw.print("fill view: ");
+                pw.println(mFillView);
+                pw.print(prefix); pw.print("mBounds: ");
+                pw.println(mBounds);
+                pw.print(prefix); pw.print("mWm: ");
+                pw.println(mWm);
+            }
+        }
+    }
+
+    /** @hide */
+    @Override
+    public void close() {
+        destroy();
+    }
+
+    private static final class FillWindowPresenter extends IAutofillWindowPresenter.Stub {
+        private final @NonNull WeakReference<FillWindow> mFillWindowReference;
+
+        FillWindowPresenter(@NonNull FillWindow fillWindow) {
+            mFillWindowReference = new WeakReference<>(fillWindow);
+        }
+
+        @Override
+        public void show(WindowManager.LayoutParams p, Rect transitionEpicenter,
+                boolean fitsSystemWindows, int layoutDirection) {
+            if (sDebug) Log.d(TAG, "FillWindowPresenter.show()");
+            final FillWindow fillWindow = mFillWindowReference.get();
+            if (fillWindow != null) {
+                fillWindow.mUiThreadHandler.sendMessage(
+                        obtainMessage(FillWindow::handleShow, fillWindow, p));
+            }
+        }
+
+        @Override
+        public void hide(Rect transitionEpicenter) {
+            if (sDebug) Log.d(TAG, "FillWindowPresenter.hide()");
+            final FillWindow fillWindow = mFillWindowReference.get();
+            if (fillWindow != null) {
+                fillWindow.mUiThreadHandler.sendMessage(
+                        obtainMessage(FillWindow::handleHide, fillWindow));
+            }
+        }
+    }
+}
diff --git a/android/service/autofill/augmented/Helper.java b/android/service/autofill/augmented/Helper.java
new file mode 100644
index 0000000..afcd8b7
--- /dev/null
+++ b/android/service/autofill/augmented/Helper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 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.augmented;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.metrics.LogMaker;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+/** @hide */
+public final class Helper {
+
+    private static final MetricsLogger sMetricsLogger = new MetricsLogger();
+
+    /**
+     * Logs a {@code MetricsEvent.AUTOFILL_AUGMENTED_RESPONSE} event.
+     */
+    public static void logResponse(int type, @NonNull String servicePackageName,
+            @NonNull ComponentName componentName, int mSessionId, long durationMs) {
+        final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_AUGMENTED_RESPONSE)
+                .setType(type)
+                .setComponentName(componentName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SESSION_ID, mSessionId)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, durationMs);
+        sMetricsLogger.write(log);
+    }
+
+    private Helper() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
+}
diff --git a/android/service/autofill/augmented/PresentationParams.java b/android/service/autofill/augmented/PresentationParams.java
new file mode 100644
index 0000000..8b3a001
--- /dev/null
+++ b/android/service/autofill/augmented/PresentationParams.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill.augmented;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.graphics.Rect;
+import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
+import android.view.View;
+
+import java.io.PrintWriter;
+
+/**
+ * Abstraction of a "Smart Suggestion" component responsible to embed the autofill UI provided by
+ * the augmented autofill service.
+ *
+ * <p>The Smart Suggestion is represented by a {@link Area} object that contains the
+ * dimensions the smart suggestion window, so the service can use it to calculate the size of the
+ * view that will be passed to {@link FillWindow#update(Area, View, long)}.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public abstract class PresentationParams {
+
+    // /** @hide */
+    PresentationParams() {}
+
+    /**
+     * Gets the area of the suggestion strip for the given {@code metadata}
+     *
+     * @return strip dimensions, or {@code null} if the Smart Suggestion provider does not support
+     * suggestions strip.
+     */
+    @Nullable
+    public Area getSuggestionArea() {
+        return null;
+    }
+
+    abstract void dump(String prefix, PrintWriter pw);
+
+    /**
+     * Area associated with a {@link PresentationParams Smart Suggestions} provider.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public abstract static class Area {
+
+        /** @hide */
+        public final AutofillProxy proxy;
+
+        private final Rect mBounds;
+
+        private Area(@NonNull AutofillProxy proxy, @NonNull Rect bounds) {
+            this.proxy = proxy;
+            mBounds = bounds;
+        }
+
+        /**
+         * Gets the area boundaries.
+         */
+        @NonNull
+        public Rect getBounds() {
+            return mBounds;
+        }
+
+        @NonNull
+        @Override
+        public String toString() {
+            return mBounds.toString();
+        }
+    }
+
+    /**
+     * System-provided poup window anchored to a view.
+     *
+     * <p>Used just for debugging purposes.
+     *
+     * @hide
+     */
+    public static final class SystemPopupPresentationParams extends PresentationParams {
+        private final Area mSuggestionArea;
+
+        public SystemPopupPresentationParams(@NonNull AutofillProxy proxy, @NonNull Rect rect) {
+            mSuggestionArea = new Area(proxy, rect) {};
+        }
+
+        @Override
+        public Area getSuggestionArea() {
+            return mSuggestionArea;
+        }
+
+        @Override
+        void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+            pw.print(prefix); pw.print("area: "); pw.println(mSuggestionArea);
+        }
+    }
+}