Update samples

AutofillFramework
EmojiCompat

Test: Built locally

Bug: 117136200

Change-Id: If56c64f6d6a0a727fd9d1dd4df6ba8661df06dba
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/BaseMainFragment.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/BaseMainFragment.java
new file mode 100644
index 0000000..5be875c
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/BaseMainFragment.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 com.example.android.autofill.app;
+
+import android.support.annotation.StringRes;
+import android.support.v4.app.Fragment;
+
+public abstract class BaseMainFragment extends Fragment {
+    public abstract @StringRes int getPageTitleResId();
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/Util.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/Util.java
new file mode 100644
index 0000000..288a908
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/Util.java
@@ -0,0 +1,154 @@
+/*
+ * 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 com.example.android.autofill.app;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.app.assist.AssistStructure.WindowNode;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewStructure.HtmlInfo;
+import android.view.autofill.AutofillValue;
+
+import java.util.Arrays;
+import java.util.Set;
+
+public final class Util {
+
+    public static final String TAG = "AutofillSample";
+    public static final boolean DEBUG = true;
+    public static final boolean VERBOSE = false;
+    public static final String EXTRA_DATASET_NAME = "dataset_name";
+    public static final String EXTRA_FOR_RESPONSE = "for_response";
+
+    private static void bundleToString(StringBuilder builder, Bundle data) {
+        final Set<String> keySet = data.keySet();
+        builder.append("[Bundle with ").append(keySet.size()).append(" keys:");
+        for (String key : keySet) {
+            builder.append(' ').append(key).append('=');
+            Object value = data.get(key);
+            if ((value instanceof Bundle)) {
+                bundleToString(builder, (Bundle) value);
+            } else {
+                builder.append((value instanceof Object[])
+                        ? Arrays.toString((Object[]) value) : value);
+            }
+        }
+        builder.append(']');
+    }
+
+    public static String bundleToString(Bundle data) {
+        if (data == null) {
+            return "N/A";
+        }
+        final StringBuilder builder = new StringBuilder();
+        bundleToString(builder, data);
+        return builder.toString();
+    }
+
+    public static String getAutofillTypeAsString(int type) {
+        switch (type) {
+            case View.AUTOFILL_TYPE_TEXT:
+                return "TYPE_TEXT";
+            case View.AUTOFILL_TYPE_LIST:
+                return "TYPE_LIST";
+            case View.AUTOFILL_TYPE_NONE:
+                return "TYPE_NONE";
+            case View.AUTOFILL_TYPE_TOGGLE:
+                return "TYPE_TOGGLE";
+            case View.AUTOFILL_TYPE_DATE:
+                return "TYPE_DATE";
+        }
+        return "UNKNOWN_TYPE";
+    }
+
+    private static String getAutofillValueAndTypeAsString(AutofillValue value) {
+        if (value == null) return "null";
+
+        StringBuilder builder = new StringBuilder(value.toString()).append('(');
+        if (value.isText()) {
+            builder.append("isText");
+        } else if (value.isDate()) {
+            builder.append("isDate");
+        } else if (value.isToggle()) {
+            builder.append("isToggle");
+        } else if (value.isList()) {
+            builder.append("isList");
+        }
+        return builder.append(')').toString();
+    }
+
+    public static void dumpStructure(AssistStructure structure) {
+        int nodeCount = structure.getWindowNodeCount();
+        Log.v(TAG, "dumpStructure(): component=" + structure.getActivityComponent()
+                + " numberNodes=" + nodeCount);
+        for (int i = 0; i < nodeCount; i++) {
+            Log.v(TAG, "node #" + i);
+            WindowNode node = structure.getWindowNodeAt(i);
+            dumpNode("  ", node.getRootViewNode());
+        }
+    }
+
+    private static void dumpNode(String prefix, ViewNode node) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(prefix)
+                .append("autoFillId: ").append(node.getAutofillId())
+                .append("\tidEntry: ").append(node.getIdEntry())
+                .append("\tid: ").append(node.getId())
+                .append("\tclassName: ").append(node.getClassName())
+                .append('\n');
+
+        builder.append(prefix)
+                .append("focused: ").append(node.isFocused())
+                .append("\tvisibility").append(node.getVisibility())
+                .append("\tchecked: ").append(node.isChecked())
+                .append("\twebDomain: ").append(node.getWebDomain())
+                .append("\thint: ").append(node.getHint())
+                .append('\n');
+
+        HtmlInfo htmlInfo = node.getHtmlInfo();
+
+        if (htmlInfo != null) {
+            builder.append(prefix)
+                    .append("HTML TAG: ").append(htmlInfo.getTag())
+                    .append(" attrs: ").append(htmlInfo.getAttributes())
+                    .append('\n');
+        }
+
+        String[] afHints = node.getAutofillHints();
+        CharSequence[] options = node.getAutofillOptions();
+        builder.append(prefix).append("afType: ").append(getAutofillTypeAsString(node.getAutofillType()))
+                .append("\tafValue:")
+                .append(getAutofillValueAndTypeAsString(node.getAutofillValue()))
+                .append("\tafOptions:").append(options == null ? "N/A" : Arrays.toString(options))
+                .append("\tafHints: ").append(afHints == null ? "N/A" : Arrays.toString(afHints))
+                .append("\tinputType:").append(node.getInputType())
+                .append('\n');
+
+        int numberChildren = node.getChildCount();
+        builder.append(prefix).append("# children: ").append(numberChildren)
+                .append("\ttext: ").append(node.getText())
+                .append('\n');
+
+        Log.v(TAG, builder.toString());
+        final String prefix2 = prefix + "  ";
+        for (int i = 0; i < numberChildren; i++) {
+            Log.v(TAG, prefix + "child #" + i);
+            dumpNode(prefix2, node.getChildAt(i));
+        }
+    }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/BadViewStructureCreationSignInActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/BadViewStructureCreationSignInActivity.java
new file mode 100644
index 0000000..ddc5798
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/BadViewStructureCreationSignInActivity.java
@@ -0,0 +1,102 @@
+/*
+ * 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 com.example.android.autofill.app.antipatterns;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+
+/**
+ * This activity behaves the same as
+ * {@link com.example.android.autofill.app.commoncases.StandardSignInActivity}, except that it
+ * creates its view structure on {@link #onStart()}.
+ *
+ * <p>This is a bad pattern anyways&mdash;the view structure should be created on
+ * {@code onCreate()}&mdash;but it's aggravatted on autofill because when an autofill service
+ * requires authentication, the Android System launches a new activity to handle authentication
+ * using this activity's task. When the authentication acivity finishes, this activity is
+ * resumed, hence if {@link #onStart()} (or {@code onResume()}) re-generates the view structure,
+ * it invalidates the response sent by the autofill service, which triggers a new autofill request
+ * when a field is focused again.
+ */
+public class BadViewStructureCreationSignInActivity extends AppCompatActivity {
+
+    private EditText mUsernameEditText;
+    private EditText mPasswordEditText;
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+
+        setContentView(R.layout.login_activity);
+        mUsernameEditText = findViewById(R.id.usernameField);
+        mPasswordEditText = findViewById(R.id.passwordField);
+        findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                login();
+            }
+        });
+        findViewById(R.id.clear).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                AutofillManager afm = getSystemService(AutofillManager.class);
+                if (afm != null) {
+                    afm.cancel();
+                }
+                resetFields();
+            }
+        });
+    }
+
+    private void resetFields() {
+        mUsernameEditText.setText("");
+        mPasswordEditText.setText("");
+    }
+
+    /**
+     * Emulates a login action.
+     */
+    private void login() {
+        String username = mUsernameEditText.getText().toString();
+        String password = mPasswordEditText.getText().toString();
+        boolean valid = isValidCredentials(username, password);
+        if (valid) {
+            Intent intent = WelcomeActivity
+                    .getStartActivityIntent(BadViewStructureCreationSignInActivity.this);
+            startActivity(intent);
+            finish();
+        } else {
+            Toast.makeText(this, "Authentication failed.", Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /**
+     * Dummy implementation for demo purposes. A real service should use secure mechanisms to
+     * authenticate users.
+     */
+    public boolean isValidCredentials(String username, String password) {
+        return username != null && password != null && username.equalsIgnoreCase(password);
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/CommonCasesFragment.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/CommonCasesFragment.java
new file mode 100644
index 0000000..ebdb3fc
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/CommonCasesFragment.java
@@ -0,0 +1,39 @@
+/*
+* 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 com.example.android.autofill.app.commoncases;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.example.android.autofill.app.BaseMainFragment;
+import com.example.android.autofill.app.R;
+
+public class CommonCasesFragment extends BaseMainFragment {
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.fragment_common_cases, container, false);
+    }
+
+    @Override
+    public int getPageTitleResId() {
+        return R.string.common_cases_page_title;
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/CreditCardCompoundViewActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/CreditCardCompoundViewActivity.java
new file mode 100644
index 0000000..b8df27d
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/CreditCardCompoundViewActivity.java
@@ -0,0 +1,78 @@
+/*
+ * 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 com.example.android.autofill.app.commoncases;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.EditText;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+import com.example.android.autofill.app.view.autofillable.CreditCardExpirationDateCompoundView;
+
+public class CreditCardCompoundViewActivity extends AppCompatActivity {
+
+    private CreditCardExpirationDateCompoundView mCcExpDateView;
+    private EditText mCcExpNumber;
+    private EditText mCcSecurityCode;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.credit_card_compound_view_activity);
+        mCcExpDateView = findViewById(R.id.creditCardExpirationView);
+        mCcExpNumber = findViewById(R.id.creditCardNumberField);
+        mCcSecurityCode = findViewById(R.id.creditCardSecurityCode);
+        findViewById(R.id.submitButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                submit();
+            }
+        });
+        findViewById(R.id.clearButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                AutofillManager afm = getSystemService(AutofillManager.class);
+                if (afm != null) {
+                    afm.cancel();
+                }
+                resetFields();
+            }
+        });
+    }
+
+    private void resetFields() {
+        mCcExpDateView.reset();
+        mCcExpNumber.setText("");
+        mCcSecurityCode.setText("");
+    }
+
+    /**
+     * Launches new Activity and finishes, triggering an autofill save request if the user entered
+     * any new data.
+     */
+    private void submit() {
+        Intent intent = WelcomeActivity.getStartActivityIntent(this);
+        startActivity(intent);
+        finish();
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/CreditCardDatePickerActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/CreditCardDatePickerActivity.java
new file mode 100644
index 0000000..4a15886
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/CreditCardDatePickerActivity.java
@@ -0,0 +1,91 @@
+/*
+ * 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 com.example.android.autofill.app.commoncases;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.EditText;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+import com.example.android.autofill.app.view.autofillable.CreditCardExpirationDatePickerView;
+
+import static com.example.android.autofill.app.Util.TAG;
+
+public class CreditCardDatePickerActivity extends AppCompatActivity {
+
+    private CreditCardExpirationDatePickerView mCcExpDateView;
+    private EditText mCcExpNumber;
+    private EditText mCcSecurityCode;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.credit_card_date_picker_activity);
+        mCcExpDateView = findViewById(R.id.creditCardExpirationView);
+        mCcExpNumber = findViewById(R.id.creditCardNumberField);
+        mCcSecurityCode = findViewById(R.id.creditCardSecurityCode);
+        findViewById(R.id.submitButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                submit();
+            }
+        });
+        findViewById(R.id.clearButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                AutofillManager afm = getSystemService(AutofillManager.class);
+                if (afm != null) {
+                    afm.cancel();
+                }
+                resetFields();
+            }
+        });
+
+        mCcExpDateView.reset();
+    }
+
+    private void resetFields() {
+        mCcExpDateView.reset();
+        mCcExpNumber.setText("");
+        mCcSecurityCode.setText("");
+    }
+
+    public void showDatePickerDialog(View v) {
+        if (v != mCcExpDateView) {
+            Log.w(TAG, "showDatePickerDialog() called on invalid view: " + v);
+            return;
+        }
+        mCcExpDateView.showDatePickerDialog(getSupportFragmentManager());
+    }
+
+
+    /**
+     * Launches new Activity and finishes, triggering an autofill save request if the user entered
+     * any new data.
+     */
+    private void submit() {
+        Intent intent = WelcomeActivity.getStartActivityIntent(this);
+        startActivity(intent);
+        finish();
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/CreditCardSpinnersActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/CreditCardSpinnersActivity.java
new file mode 100644
index 0000000..bab5379
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/CreditCardSpinnersActivity.java
@@ -0,0 +1,123 @@
+/*
+ * 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 com.example.android.autofill.app.commoncases;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.Spinner;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+
+import java.util.Calendar;
+
+public class CreditCardSpinnersActivity extends AppCompatActivity {
+
+    private static final int CC_EXP_YEARS_COUNT = 5;
+
+    private final String[] years = new String[CC_EXP_YEARS_COUNT];
+
+    private Spinner mCcExpirationDaySpinner;
+    private Spinner mCcExpirationMonthSpinner;
+    private Spinner mCcExpirationYearSpinner;
+    private EditText mCcCardNumber;
+    private EditText mCcSecurityCode;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.credit_card_spinners_activity);
+        mCcExpirationDaySpinner = findViewById(R.id.expirationDay);
+        mCcExpirationMonthSpinner = findViewById(R.id.expirationMonth);
+        mCcExpirationYearSpinner = findViewById(R.id.expirationYear);
+        mCcCardNumber = findViewById(R.id.creditCardNumberField);
+        mCcSecurityCode = findViewById(R.id.creditCardSecurityCode);
+
+        // Create an ArrayAdapter using the string array and a default spinner layout
+        ArrayAdapter<CharSequence> dayAdapter = ArrayAdapter.createFromResource
+                (this, R.array.day_array, android.R.layout.simple_spinner_item);
+        // Specify the layout to use when the list of choices appears
+        dayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        // Apply the adapter to the spinner
+        mCcExpirationDaySpinner.setAdapter(dayAdapter);
+
+        /*
+        R.array.month_array could be an array of Strings like "Jan", "Feb", "March", etc., and
+        the AutofillService would know how to autofill it. However, for the sake of keeping the
+        AutofillService simple, we will stick to a list of numbers (1, 2, ... 12) to represent
+        months; it makes it much easier to generate fake autofill data in the service that can still
+        autofill this spinner.
+        */
+        ArrayAdapter<CharSequence> monthAdapter = ArrayAdapter.createFromResource(
+                this, R.array.month_array, android.R.layout.simple_spinner_item);
+        // Adapter created from resource has getAutofillOptions() implemented by default.
+        monthAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        mCcExpirationMonthSpinner.setAdapter(monthAdapter);
+
+        int year = Calendar.getInstance().get(Calendar.YEAR);
+        for (int i = 0; i < years.length; i++) {
+            years[i] = Integer.toString(year + i);
+        }
+        // Since the years Spinner uses a custom adapter, it needs to implement getAutofillOptions.
+        mCcExpirationYearSpinner.setAdapter(
+                new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, years) {
+                    @Override
+                    public CharSequence[] getAutofillOptions() {
+                        return years;
+                    }
+                });
+        findViewById(R.id.submit).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                submit();
+            }
+        });
+        findViewById(R.id.clear).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                AutofillManager afm = getSystemService(AutofillManager.class);
+                if (afm != null) {
+                    afm.cancel();
+                }
+                resetFields();
+            }
+        });
+    }
+
+    private void resetFields() {
+        mCcExpirationDaySpinner.setSelection(0);
+        mCcExpirationMonthSpinner.setSelection(0);
+        mCcExpirationYearSpinner.setSelection(0);
+        mCcCardNumber.setText("");
+        mCcSecurityCode.setText("");
+    }
+
+    /**
+     * Launches new Activity and finishes, triggering an autofill save request if the user entered
+     * any new data.
+     */
+    private void submit() {
+        Intent intent = WelcomeActivity.getStartActivityIntent(CreditCardSpinnersActivity.this);
+        startActivity(intent);
+        finish();
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/EmailComposeActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/EmailComposeActivity.java
new file mode 100644
index 0000000..94a0019
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/EmailComposeActivity.java
@@ -0,0 +1,42 @@
+/*
+ * 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 com.example.android.autofill.app.commoncases;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+
+public class EmailComposeActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.email_compose_activity);
+        findViewById(R.id.sendButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                startActivity(WelcomeActivity.getStartActivityIntent(EmailComposeActivity.this));
+                finish();
+            }
+        });
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/RecyclerViewActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/RecyclerViewActivity.java
new file mode 100644
index 0000000..c2ce43c
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/RecyclerViewActivity.java
@@ -0,0 +1,358 @@
+/*
+ * 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 com.example.android.autofill.app.commoncases;
+
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.StringRes;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.recyclerview.extensions.ListAdapter;
+import android.support.v7.util.DiffUtil;
+import android.support.v7.widget.DividerItemDecoration;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This is mostly a normal Activity containing a RecyclerView. The only difference is, the rows in
+ * the RecyclerView have autofillable fields. Therefore, when we bind data to a recycled view, we
+ * need to also set the {@link AutofillId} on the view.
+ */
+@RequiresApi(28)
+public class RecyclerViewActivity extends AppCompatActivity {
+    private AutofillManager mAfm;
+    private List<FieldMetadata> mFields;
+    private FieldAdapter mFieldAdapter;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.recycler_view_activity);
+        RecyclerView recyclerView = findViewById(R.id.recyclerView);
+        mAfm = getSystemService(AutofillManager.class);
+
+        // Init RecyclerView
+        LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
+        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(
+                recyclerView.getContext(), layoutManager.getOrientation());
+        recyclerView.addItemDecoration(dividerItemDecoration);
+        mFields = initList();
+        mFieldAdapter = new FieldAdapter(mFields);
+        recyclerView.setAdapter(mFieldAdapter);
+        recyclerView.setLayoutManager(layoutManager);
+
+        // Init submit and clear buttons.
+        findViewById(R.id.submitButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                submit();
+            }
+        });
+        findViewById(R.id.clearButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                AutofillManager afm = getSystemService(AutofillManager.class);
+                if (afm != null) {
+                    afm.cancel();
+                }
+                resetFields();
+            }
+        });
+    }
+
+    private void resetFields() {
+        for (FieldMetadata fieldMetadata : mFields) {
+            fieldMetadata.setEnteredText("");
+        }
+        mFieldAdapter.notifyDataSetChanged();
+    }
+
+    private void submit() {
+        Intent intent = WelcomeActivity.getStartActivityIntent(RecyclerViewActivity.this);
+        startActivity(intent);
+        finish();
+    }
+
+    public List<FieldMetadata> initList() {
+        return Arrays.asList(
+                new FieldMetadata(
+                        mAfm.getNextAutofillId(),
+                        View.AUTOFILL_HINT_NAME,
+                        R.string.recycler_view_label_name,
+                        R.drawable.ic_person_black_24dp,
+                        InputType.TYPE_CLASS_TEXT
+                ),
+                new FieldMetadata(
+                        mAfm.getNextAutofillId(),
+                        "bday-month",
+                        R.string.recycler_view_label_birthday_month,
+                        R.drawable.ic_person_black_24dp,
+                        InputType.TYPE_CLASS_NUMBER
+                ),
+                new FieldMetadata(
+                        mAfm.getNextAutofillId(),
+                        View.AUTOFILL_HINT_EMAIL_ADDRESS,
+                        R.string.recycler_view_label_email,
+                        R.drawable.ic_person_black_24dp,
+                        InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+                ),
+                new FieldMetadata(
+                        mAfm.getNextAutofillId(),
+                        View.AUTOFILL_HINT_PHONE,
+                        R.string.recycler_view_label_phone,
+                        R.drawable.ic_person_black_24dp,
+                        InputType.TYPE_CLASS_PHONE
+                ),
+                new FieldMetadata(
+                        mAfm.getNextAutofillId(),
+                        "tel_extension",
+                        R.string.recycler_view_label_tel_extension,
+                        R.drawable.ic_person_black_24dp,
+                        InputType.TYPE_CLASS_PHONE
+                ),
+                new FieldMetadata(
+                        mAfm.getNextAutofillId(),
+                        View.AUTOFILL_HINT_CREDIT_CARD_NUMBER,
+                        R.string.recycler_view_label_cc_number,
+                        R.drawable.ic_person_black_24dp,
+                        InputType.TYPE_CLASS_NUMBER
+                ),
+                new FieldMetadata(
+                        mAfm.getNextAutofillId(),
+                        View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE,
+                        R.string.recycler_view_label_cc_sc,
+                        R.drawable.ic_person_black_24dp,
+                        InputType.TYPE_CLASS_NUMBER
+                ),
+                new FieldMetadata(
+                        mAfm.getNextAutofillId(),
+                        View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH,
+                        R.string.recycler_view_label_cc_exp_month,
+                        R.drawable.ic_person_black_24dp,
+                        InputType.TYPE_CLASS_NUMBER
+                ),
+                new FieldMetadata(
+                        mAfm.getNextAutofillId(),
+                        View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR,
+                        R.string.recycler_view_label_cc_exp_year,
+                        R.drawable.ic_person_black_24dp,
+                        InputType.TYPE_CLASS_NUMBER
+                ),
+                new FieldMetadata(
+                        mAfm.getNextAutofillId(),
+                        "address-line1",
+                        R.string.recycler_view_label_address_line_1,
+                        R.drawable.ic_person_black_24dp,
+                        InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS
+                ),
+                new FieldMetadata(
+                        mAfm.getNextAutofillId(),
+                        "address-line2",
+                        R.string.recycler_view_label_address_line_2,
+                        R.drawable.ic_person_black_24dp,
+                        InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS
+                ),
+                new FieldMetadata(
+                        mAfm.getNextAutofillId(),
+                        "address-line3",
+                        R.string.recycler_view_label_address_line_3,
+                        R.drawable.ic_person_black_24dp,
+                        InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS
+                ),
+                new FieldMetadata(
+                        mAfm.getNextAutofillId(),
+                        View.AUTOFILL_HINT_POSTAL_CODE,
+                        R.string.recycler_view_label_postal_code,
+                        R.drawable.ic_person_black_24dp,
+                        InputType.TYPE_CLASS_NUMBER
+                ),
+                new FieldMetadata(
+                        mAfm.getNextAutofillId(),
+                        "bday-year",
+                        R.string.recycler_view_label_birthday_year,
+                        R.drawable.ic_person_black_24dp,
+                        InputType.TYPE_CLASS_NUMBER
+                )
+        );
+    }
+
+    static class FieldAdapter extends ListAdapter<FieldMetadata, FieldViewHolder> {
+        private List<FieldMetadata> mFields;
+        public FieldAdapter(List<FieldMetadata> fields) {
+            super(new FieldDiff());
+            mFields = fields;
+            submitList(mFields);
+        }
+
+        @NonNull
+        @Override
+        public FieldViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+            return new FieldViewHolder(LayoutInflater.from(parent.getContext())
+                    .inflate(R.layout.user_data_field, parent, false),
+                    new FieldWatcher(mFields));
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull FieldViewHolder holder, int position) {
+            holder.bind(getItem(position));
+        }
+    }
+
+    static class FieldViewHolder extends RecyclerView.ViewHolder {
+        ImageView mIcon;
+        TextView mLabel;
+        EditText mField;
+        FieldWatcher mWatcher;
+
+        public FieldViewHolder(@NonNull View itemView, FieldWatcher textWatcher) {
+            super(itemView);
+            mIcon = itemView.findViewById(R.id.icon);
+            mLabel = itemView.findViewById(R.id.label);
+            mField = itemView.findViewById(R.id.field);
+            mWatcher = textWatcher;
+            mField.addTextChangedListener(mWatcher);
+        }
+
+        void bind(FieldMetadata fieldMetadata) {
+            mWatcher.updatePosition(getAdapterPosition());
+            Drawable drawable = mIcon.getResources().getDrawable(fieldMetadata.getIconRes());
+            mIcon.setImageDrawable(drawable);
+            mLabel.setText(fieldMetadata.getLabelRes());
+            mField.setAutofillHints(fieldMetadata.getAutofillHint());
+            mField.setInputType(fieldMetadata.getInputType());
+            mField.setText(fieldMetadata.getEnteredText());
+
+            // IMPORTANT: setAutofillId of recycled View.
+            mField.setAutofillId(fieldMetadata.getAutofillId());
+        }
+    }
+
+    /**
+     * TextWatcher implementation to ensure EditTexts get recycled properly.
+     */
+    static class FieldWatcher implements TextWatcher {
+        private int mPosition;
+        private List<FieldMetadata> mFields;
+
+        public FieldWatcher(List<FieldMetadata> fields) {
+            mFields = fields;
+        }
+
+        public void updatePosition(int position) {
+            this.mPosition = position;
+        }
+
+        @Override
+        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+            // NO-OP
+        }
+
+        @Override
+        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+            mFields.get(mPosition).setEnteredText(charSequence);
+        }
+
+        @Override
+        public void afterTextChanged(Editable editable) {
+            // NO-OP
+        }
+    }
+
+    /**
+     * Model class that holds all of the data needed for a row in the {@link RecyclerView}.
+     */
+    static class FieldMetadata {
+        AutofillId mAutofillId;
+        String mAutofillHint;
+        @StringRes int mLabelRes;
+        @DrawableRes int mIconRes;
+        int mInputType;
+        CharSequence mEnteredText = "";
+
+        FieldMetadata(AutofillId autofillId, String autofillHint, @StringRes int labelRes,
+                @DrawableRes int iconRes, int inputType) {
+            mAutofillId = autofillId;
+            mAutofillHint = autofillHint;
+            mLabelRes = labelRes;
+            mIconRes = iconRes;
+            mInputType = inputType;
+        }
+
+        public AutofillId getAutofillId() {
+            return mAutofillId;
+        }
+
+        public String getAutofillHint() {
+            return mAutofillHint;
+        }
+
+        public int getLabelRes() {
+            return mLabelRes;
+        }
+
+        public int getIconRes() {
+            return mIconRes;
+        }
+
+        public int getInputType() {
+            return mInputType;
+        }
+
+        public void setEnteredText(CharSequence enteredText) {
+            mEnteredText = enteredText;
+        }
+
+        public CharSequence getEnteredText() {
+            return mEnteredText;
+        }
+    }
+
+    static class FieldDiff extends DiffUtil.ItemCallback<FieldMetadata> {
+        @Override
+        public boolean areItemsTheSame(@NonNull FieldMetadata oldItem,
+                @NonNull FieldMetadata newItem) {
+            return oldItem == newItem;
+        }
+
+        @Override
+        public boolean areContentsTheSame(@NonNull FieldMetadata oldItem,
+                @NonNull FieldMetadata newItem) {
+            return oldItem.equals(newItem);
+        }
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardAutoCompleteSignInActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardAutoCompleteSignInActivity.java
new file mode 100644
index 0000000..089c6b2
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardAutoCompleteSignInActivity.java
@@ -0,0 +1,142 @@
+/*
+ * 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 com.example.android.autofill.app.commoncases;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+
+import static com.example.android.autofill.app.Util.TAG;
+
+public class StandardAutoCompleteSignInActivity extends AppCompatActivity {
+    private AutoCompleteTextView mUsernameAutoCompleteField;
+    private TextView mPasswordField;
+    private TextView mLoginButton;
+    private TextView mClearButton;
+    private boolean mAutofillReceived = false;
+    private AutofillManager.AutofillCallback mAutofillCallback;
+    private AutofillManager mAutofillManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.login_with_autocomplete_activity);
+
+        mLoginButton = findViewById(R.id.login);
+        mClearButton = findViewById(R.id.clear);
+        mUsernameAutoCompleteField = findViewById(R.id.usernameField);
+        mPasswordField = findViewById(R.id.passwordField);
+        mLoginButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                login();
+            }
+        });
+        mClearButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                AutofillManager afm = getSystemService(AutofillManager.class);
+                if (afm != null) {
+                    afm.cancel();
+                }
+                resetFields();
+            }
+        });
+        mAutofillCallback = new MyAutofillCallback();
+        mAutofillManager = getSystemService(AutofillManager.class);
+        ArrayAdapter<CharSequence> mockAutocompleteAdapter = ArrayAdapter.createFromResource
+                (this, R.array.mock_autocomplete_sign_in_suggestions,
+                        android.R.layout.simple_dropdown_item_1line);
+        mUsernameAutoCompleteField.setAdapter(mockAutocompleteAdapter);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mAutofillManager.registerCallback(mAutofillCallback);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mAutofillManager.unregisterCallback(mAutofillCallback);
+    }
+
+    private void resetFields() {
+        mUsernameAutoCompleteField.setText("");
+        mPasswordField.setText("");
+    }
+
+    /**
+     * Emulates a login action.
+     */
+    private void login() {
+        String username = mUsernameAutoCompleteField.getText().toString();
+        String password = mPasswordField.getText().toString();
+        boolean valid = isValidCredentials(username, password);
+        if (valid) {
+            Intent intent = WelcomeActivity.getStartActivityIntent(StandardAutoCompleteSignInActivity.this);
+            startActivity(intent);
+            finish();
+        } else {
+            Toast.makeText(this, "Authentication failed.", Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /**
+     * Dummy implementation for demo purposes. A real service should use secure mechanisms to
+     * authenticate users.
+     */
+    public boolean isValidCredentials(String username, String password) {
+        return username != null && password != null && username.equals(password);
+    }
+
+    private class MyAutofillCallback extends AutofillManager.AutofillCallback {
+        @Override
+        public void onAutofillEvent(@NonNull View view, int event) {
+            if (view instanceof AutoCompleteTextView) {
+                switch (event) {
+                    case AutofillManager.AutofillCallback.EVENT_INPUT_UNAVAILABLE:
+                        // no break on purpose
+                    case AutofillManager.AutofillCallback.EVENT_INPUT_HIDDEN:
+                        if (!mAutofillReceived) {
+                            ((AutoCompleteTextView) view).showDropDown();
+                        }
+                        break;
+                    case AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN:
+                        mAutofillReceived = true;
+                        ((AutoCompleteTextView) view).setAdapter(null);
+                        break;
+                    default:
+                        Log.d(TAG, "Unexpected callback: " + event);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardSignInActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardSignInActivity.java
new file mode 100644
index 0000000..c333bce
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardSignInActivity.java
@@ -0,0 +1,88 @@
+/*
+ * 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 com.example.android.autofill.app.commoncases;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+
+public class StandardSignInActivity extends AppCompatActivity {
+
+    private EditText mUsernameEditText;
+    private EditText mPasswordEditText;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.login_activity);
+        mUsernameEditText = findViewById(R.id.usernameField);
+        mPasswordEditText = findViewById(R.id.passwordField);
+        findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                login();
+            }
+        });
+        findViewById(R.id.clear).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                AutofillManager afm = getSystemService(AutofillManager.class);
+                if (afm != null) {
+                    afm.cancel();
+                }
+                resetFields();
+            }
+        });
+    }
+
+    private void resetFields() {
+        mUsernameEditText.setText("");
+        mPasswordEditText.setText("");
+    }
+
+    /**
+     * Emulates a login action.
+     */
+    private void login() {
+        String username = mUsernameEditText.getText().toString();
+        String password = mPasswordEditText.getText().toString();
+        boolean valid = isValidCredentials(username, password);
+        if (valid) {
+            Intent intent = WelcomeActivity.getStartActivityIntent(StandardSignInActivity.this);
+            startActivity(intent);
+            finish();
+        } else {
+            Toast.makeText(this, "Authentication failed.", Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /**
+     * Dummy implementation for demo purposes. A real service should use secure mechanisms to
+     * authenticate users.
+     */
+    public boolean isValidCredentials(String username, String password) {
+        return username != null && password != null && username.equalsIgnoreCase(password);
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/VirtualSignInActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/VirtualSignInActivity.java
new file mode 100644
index 0000000..baafbf7
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/VirtualSignInActivity.java
@@ -0,0 +1,102 @@
+/*
+ * 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 com.example.android.autofill.app.commoncases;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.Toast;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+import com.example.android.autofill.app.view.autofillable.CustomVirtualView;
+
+/**
+ * Activity that uses a virtual views for Username/Password text fields.
+ */
+public class VirtualSignInActivity extends AppCompatActivity {
+
+    private CustomVirtualView mCustomVirtualView;
+    private AutofillManager mAutofillManager;
+    private CustomVirtualView.Line mUsernameLine;
+    private CustomVirtualView.Line mPasswordLine;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.virtual_login_activity);
+
+        mCustomVirtualView = (CustomVirtualView) findViewById(R.id.custom_view);
+
+        CustomVirtualView.Partition credentialsPartition =
+                mCustomVirtualView.addPartition(getString(R.string.partition_credentials));
+        mUsernameLine = credentialsPartition.addLine("username", View.AUTOFILL_TYPE_TEXT,
+                getString(R.string.username_label),
+                "         ", false, View.AUTOFILL_HINT_USERNAME);
+        mPasswordLine = credentialsPartition.addLine("password", View.AUTOFILL_TYPE_TEXT,
+                getString(R.string.password_label),
+                "         ", true, View.AUTOFILL_HINT_PASSWORD);
+
+        findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                login();
+            }
+        });
+        findViewById(R.id.clear).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                resetFields();
+                mAutofillManager.cancel();
+            }
+        });
+        mAutofillManager = getSystemService(AutofillManager.class);
+    }
+
+    private void resetFields() {
+        mUsernameLine.reset();
+        mPasswordLine.reset();
+        mCustomVirtualView.postInvalidate();
+    }
+
+    /**
+     * Emulates a login action.
+     */
+    private void login() {
+        String username = mUsernameLine.getText().toString();
+        String password = mPasswordLine.getText().toString();
+        boolean valid = isValidCredentials(username, password);
+        if (valid) {
+            Intent intent = WelcomeActivity.getStartActivityIntent(VirtualSignInActivity.this);
+            startActivity(intent);
+            finish();
+        } else {
+            Toast.makeText(this, "Authentication failed.", Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /**
+     * Dummy implementation for demo purposes. A real service should use secure mechanisms to
+     * authenticate users.
+     */
+    public boolean isValidCredentials(String username, String password) {
+        return username != null && password != null && username.equals(password);
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/WebViewSignInActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/WebViewSignInActivity.java
new file mode 100644
index 0000000..9bd09f7
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/WebViewSignInActivity.java
@@ -0,0 +1,57 @@
+/*
+* 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 com.example.android.autofill.app.commoncases;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import com.example.android.autofill.app.R;
+
+import static com.example.android.autofill.app.Util.DEBUG;
+import static com.example.android.autofill.app.Util.TAG;
+
+public class WebViewSignInActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.login_webview_activity);
+
+        WebView webView = findViewById(R.id.webview);
+        WebSettings webSettings = webView.getSettings();
+        webView.setWebViewClient(new WebViewClient());
+        webSettings.setJavaScriptEnabled(true);
+
+        String url = getIntent().getStringExtra("url");
+        if (url == null) {
+            url = "file:///android_res/raw/sample_form.html";
+        }
+        if (DEBUG) Log.d(TAG, "Clearing WebView data");
+        webView.clearHistory();
+        webView.clearFormData();
+        webView.clearCache(true);
+        Log.i(TAG, "Loading URL " + url);
+        webView.loadUrl(url);
+    }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/AbstractMultipleStepsActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/AbstractMultipleStepsActivity.java
new file mode 100644
index 0000000..2baf335
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/AbstractMultipleStepsActivity.java
@@ -0,0 +1,174 @@
+/*
+ * 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 com.example.android.autofill.app.edgecases;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.example.android.autofill.app.R;
+
+import java.util.Map;
+
+import static com.example.android.autofill.app.Util.DEBUG;
+import static com.example.android.autofill.app.Util.TAG;
+
+/**
+ * Activity that emulates a multiple-steps wizard activity, where each step shows just one
+ * label and input.
+ * <p>
+ * <p>Its's useful to verify how an autofill service handles account creation that takes multiple
+ * steps.
+ */
+
+/*
+ * TODO list
+ * - use ConstraintLayout
+ * - use Fragments instead of replacing views directly
+ * - use custom view and/or layout xml for mSteps
+ */
+abstract class AbstractMultipleStepsActivity extends AppCompatActivity {
+
+    private TextView mStatus;
+    private ViewGroup mContainer;
+
+    private Button mPrev;
+    private Button mNext;
+    private Button mFinish;
+
+    private int mCurrentStep;
+    private boolean mFinished;
+
+    private LinearLayout[] mSteps;
+
+    /**
+     * Gets the mapping from resource id to autofill hints.
+     */
+    protected abstract Map<Integer, String> getStepsMap();
+
+    @Override
+    protected final void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.multiple_steps_activity);
+
+        mStatus = findViewById(R.id.status);
+        mContainer = findViewById(R.id.container);
+        mPrev = findViewById(R.id.prev);
+        mNext = findViewById(R.id.next);
+        mFinish = findViewById(R.id.finish);
+
+        View.OnClickListener onClickListener = (v) -> {
+            if (v == mPrev) {
+                showStep(mCurrentStep - 1);
+            } else if (v == mNext) {
+                showStep(mCurrentStep + 1);
+            } else {
+                finishIt();
+            }
+        };
+        mPrev.setOnClickListener(onClickListener);
+        mNext.setOnClickListener(onClickListener);
+        mFinish.setOnClickListener(onClickListener);
+
+        Map<Integer, String> stepsMap = getStepsMap();
+        if (DEBUG) debug("onCreate(): steps=%s", stepsMap);
+        initializeSteps(stepsMap);
+
+        showStep(0);
+    }
+
+    private void showStep(int i) {
+        if (mFinished || i < 0 || i >= mSteps.length) {
+            warn("Invalid step: %d (finished=%s, range=[%d,%d])",
+                    mFinished, i, 0, mSteps.length - 1);
+            return;
+        }
+        View step = mSteps[i];
+        mStatus.setText(getString(R.string.message_showing_step, i));
+        if (DEBUG) debug("Showing step %d", i);
+        if (mContainer.getChildCount() > 0) {
+            mContainer.removeViewAt(0);
+        }
+        mContainer.addView(step);
+        mCurrentStep = i;
+
+        mPrev.setEnabled(mCurrentStep != 0);
+        mNext.setEnabled(mCurrentStep != mSteps.length - 1);
+    }
+
+    private void updateButtons() {
+        mPrev.setEnabled(!mFinished && mCurrentStep != 0);
+        mNext.setEnabled(!mFinished && mCurrentStep != mSteps.length - 1);
+        mFinish.setEnabled(!mFinished);
+    }
+
+    private void finishIt() {
+        StringBuilder message = new StringBuilder(getString(R.string.message_finished))
+                .append("\n\n");
+        for (int i = 0; i < mSteps.length; i++) {
+            TextView label = (TextView) mSteps[i].getChildAt(0);
+            EditText input = (EditText) mSteps[i].getChildAt(1);
+            message.append(getString(R.string.message_step_description, label.getText(), input.getText()))
+                    .append('\n');
+        }
+        mStatus.setText(message.toString());
+        mContainer.removeAllViews();
+        mFinished = true;
+        updateButtons();
+    }
+
+    private void initializeSteps(Map<Integer, String> stepsMap) {
+        mSteps = new LinearLayout[stepsMap.size()];
+        int i = 0;
+        for (Map.Entry<Integer, String> entry : stepsMap.entrySet()) {
+            int labelId = entry.getKey();
+            String autofillHints = entry.getValue();
+            if (DEBUG) debug("step %d: %s->%s", i, getString(labelId), autofillHints);
+            mSteps[i++] = newStep(labelId, autofillHints);
+        }
+    }
+
+    private LinearLayout newStep(int labelId, String autofillHints) {
+        LinearLayout layout = new LinearLayout(this);
+        layout.setOrientation(LinearLayout.HORIZONTAL);
+
+        TextView label = new TextView(this);
+        label.setText(labelId);
+        layout.addView(label);
+
+        EditText input = new EditText(this);
+        input.setAutofillHints(autofillHints);
+        input.setWidth(500); // TODO: proper size
+        layout.addView(input);
+
+        return layout;
+    }
+
+    protected void debug(String fmt, Object... args) {
+        Log.d(TAG, getLocalClassName() + "." + String.format(fmt, args));
+    }
+
+    protected void warn(String fmt, Object... args) {
+        Log.w(TAG, getLocalClassName() + "." + String.format(fmt, args));
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CreditCardActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CreditCardActivity.java
new file mode 100644
index 0000000..3e68a01
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CreditCardActivity.java
@@ -0,0 +1,82 @@
+/*
+ * 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 com.example.android.autofill.app.edgecases;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.EditText;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+
+public class CreditCardActivity extends AppCompatActivity {
+
+    private EditText mCcExpDayView;
+    private EditText mCcExpMonthView;
+    private EditText mCcExpYearView;
+    private EditText mCcNumber;
+    private EditText mCcSecurityCode;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.credit_card_activity);
+        mCcExpDayView = findViewById(R.id.expirationDay);
+        mCcExpMonthView = findViewById(R.id.expirationMonth);
+        mCcExpYearView = findViewById(R.id.expirationYear);
+        mCcNumber = findViewById(R.id.creditCardNumberField);
+        mCcSecurityCode = findViewById(R.id.creditCardSecurityCode);
+        findViewById(R.id.submitButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                submit();
+            }
+        });
+        findViewById(R.id.clearButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                AutofillManager afm = getSystemService(AutofillManager.class);
+                if (afm != null) {
+                    afm.cancel();
+                }
+                resetFields();
+            }
+        });
+    }
+
+    private void resetFields() {
+        mCcExpDayView.setText("");
+        mCcExpMonthView.setText("");
+        mCcExpYearView.setText("");
+        mCcNumber.setText("");
+        mCcSecurityCode.setText("");
+    }
+
+    /**
+     * Launches new Activity and finishes, triggering an autofill save request if the user entered
+     * any new data.
+     */
+    private void submit() {
+        Intent intent = WelcomeActivity.getStartActivityIntent(this);
+        startActivity(intent);
+        finish();
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CreditCardAntiPatternActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CreditCardAntiPatternActivity.java
new file mode 100644
index 0000000..edffcc0
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CreditCardAntiPatternActivity.java
@@ -0,0 +1,76 @@
+/*
+ * 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 com.example.android.autofill.app.edgecases;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.EditText;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+
+public class CreditCardAntiPatternActivity extends AppCompatActivity {
+
+    private EditText mCcExpDateView;
+    private EditText mCcExpNumber;
+    private EditText mCcSecurityCode;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.credit_card_anti_pattern_activity);
+        mCcExpDateView = findViewById(R.id.creditCardExpirationView);
+        mCcExpNumber = findViewById(R.id.creditCardNumberField);
+        mCcSecurityCode = findViewById(R.id.creditCardSecurityCode);
+        findViewById(R.id.submitButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                submit();
+            }
+        });
+        findViewById(R.id.clearButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                AutofillManager afm = getSystemService(AutofillManager.class);
+                if (afm != null) {
+                    afm.cancel();
+                }
+                resetFields();
+            }
+        });
+    }
+
+    private void resetFields() {
+        mCcExpDateView.setText("");
+        mCcExpNumber.setText("");
+        mCcSecurityCode.setText("");
+    }
+
+    /**
+     * Launches new Activity and finishes, triggering an autofill save request if the user entered
+     * any new data.
+     */
+    private void submit() {
+        Intent intent = WelcomeActivity.getStartActivityIntent(this);
+        startActivity(intent);
+        finish();
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/EdgeCasesFragment.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/EdgeCasesFragment.java
new file mode 100644
index 0000000..917be10
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/EdgeCasesFragment.java
@@ -0,0 +1,40 @@
+/*
+* 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 com.example.android.autofill.app.edgecases;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.example.android.autofill.app.BaseMainFragment;
+import com.example.android.autofill.app.R;
+
+public class EdgeCasesFragment extends BaseMainFragment {
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.fragment_edge_cases, container, false);
+    }
+
+    @Override
+    public int getPageTitleResId() {
+        return R.string.edge_cases_page_title;
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultiplePartitionsActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultiplePartitionsActivity.java
new file mode 100644
index 0000000..9e09b45
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultiplePartitionsActivity.java
@@ -0,0 +1,114 @@
+/*
+ * 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 com.example.android.autofill.app.edgecases;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.Toast;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.Util;
+import com.example.android.autofill.app.view.autofillable.CustomVirtualView;
+import com.example.android.autofill.app.view.autofillable.ScrollableCustomVirtualView;
+
+/**
+ * Activity used to demonstrated safe partitioning of data.
+ * <p>
+ * <p>It has multiple partitions, but only accepts autofill on each partition at time.
+ */
+/*
+ * TODO list
+ *
+ * - Fix top margin.
+ * - Use a combo box to select if credit card expiration date is expressed as date or text.
+ * - Use a dedicated TextView (instead of Toast) for error messages.
+ * - Use wrap_context to CustomView container.
+ * - Use different background color (or borders) for each partition.
+ * - Add more partitions (like address) - should match same partitions from service.
+ * - Add more hints (like w3c ones) - should match same hints from service.
+ */
+public class MultiplePartitionsActivity extends AppCompatActivity {
+
+    private ScrollableCustomVirtualView mCustomVirtualView;
+    private AutofillManager mAutofillManager;
+    private CustomVirtualView.Partition mCredentialsPartition;
+    private CustomVirtualView.Partition mCcPartition;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.multiple_partitions_activity);
+        mCustomVirtualView = findViewById(R.id.custom_view);
+        mCredentialsPartition =
+                mCustomVirtualView.addPartition(getString(R.string.partition_credentials));
+        mCredentialsPartition.addLine("username", View.AUTOFILL_TYPE_TEXT,
+                getString(R.string.username_label),
+                "         ", false, View.AUTOFILL_HINT_USERNAME);
+        mCredentialsPartition.addLine("password", View.AUTOFILL_TYPE_TEXT,
+                getString(R.string.password_label),
+                "         ", true, View.AUTOFILL_HINT_PASSWORD);
+        int ccExpirationType = View.AUTOFILL_TYPE_DATE;
+        // TODO: add a checkbox to switch between text / date instead
+        Intent intent = getIntent();
+        if (intent != null) {
+            int newType = intent.getIntExtra("dateType", -1);
+            if (newType != -1) {
+                ccExpirationType = newType;
+                String typeMessage = getString(R.string.message_credit_card_expiration_type,
+                        Util.getAutofillTypeAsString(ccExpirationType));
+                // TODO: display type in a header or proper status widget
+                Toast.makeText(getApplicationContext(), typeMessage, Toast.LENGTH_LONG).show();
+            }
+        }
+        mCcPartition = mCustomVirtualView.addPartition(getString(R.string.partition_credit_card));
+        mCcPartition.addLine("ccNumber", View.AUTOFILL_TYPE_TEXT,
+                getString(R.string.credit_card_number_label),
+                "         ", true, View.AUTOFILL_HINT_CREDIT_CARD_NUMBER);
+        mCcPartition.addLine("ccDay", View.AUTOFILL_TYPE_TEXT,
+                getString(R.string.credit_card_expiration_day_label),
+                "         ", true, View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY);
+        mCcPartition.addLine("ccMonth", ccExpirationType,
+                getString(R.string.credit_card_expiration_month_label),
+                "         ", true, View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH);
+        mCcPartition.addLine("ccYear", View.AUTOFILL_TYPE_TEXT,
+                getString(R.string.credit_card_expiration_year_label),
+                "         ", true, View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR);
+        mCcPartition.addLine("ccDate", ccExpirationType,
+                getString(R.string.credit_card_expiration_date_label),
+                "         ", true, View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE);
+        mCcPartition.addLine("ccSecurityCode", View.AUTOFILL_TYPE_TEXT,
+                getString(R.string.credit_card_security_code_label),
+                "         ", true, View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE);
+        mAutofillManager = getSystemService(AutofillManager.class);
+        findViewById(R.id.clear).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                resetFields();
+                mCustomVirtualView.resetPositions();
+                mAutofillManager.cancel();
+            }
+        });
+    }
+
+    private void resetFields() {
+        mCredentialsPartition.reset();
+        mCcPartition.reset();
+        mCustomVirtualView.postInvalidate();
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultipleStepsCreditCardActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultipleStepsCreditCardActivity.java
new file mode 100644
index 0000000..804b77f
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultipleStepsCreditCardActivity.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 com.example.android.autofill.app.edgecases;
+
+import android.view.View;
+
+import com.example.android.autofill.app.R;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class MultipleStepsCreditCardActivity extends AbstractMultipleStepsActivity {
+
+    @Override
+    protected Map<Integer, String> getStepsMap() {
+        LinkedHashMap<Integer, String> steps = new LinkedHashMap<>(4);
+        steps.put(R.string.credit_card_number_label,
+                View.AUTOFILL_HINT_CREDIT_CARD_NUMBER);
+        steps.put(R.string.credit_card_expiration_month_label,
+                View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH);
+        steps.put(R.string.credit_card_expiration_year_label,
+                View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR);
+        steps.put(R.string.credit_card_security_code_abbrev_label,
+                View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE);
+        return ImmutableMap.copyOf(steps);
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultipleStepsSignInActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultipleStepsSignInActivity.java
new file mode 100644
index 0000000..ec45a9c
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultipleStepsSignInActivity.java
@@ -0,0 +1,35 @@
+/*
+ * 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 com.example.android.autofill.app.edgecases;
+
+import android.view.View;
+
+import com.example.android.autofill.app.R;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class MultipleStepsSignInActivity extends AbstractMultipleStepsActivity {
+
+    @Override
+    protected Map<Integer, String> getStepsMap() {
+        LinkedHashMap<Integer, String> steps = new LinkedHashMap<>(2);
+        steps.put(R.string.username_label, View.AUTOFILL_HINT_USERNAME);
+        steps.put(R.string.password_label, View.AUTOFILL_HINT_PASSWORD);
+        return ImmutableMap.copyOf(steps);
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/VirtualCompatModeSignInActivity.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/VirtualCompatModeSignInActivity.java
new file mode 100644
index 0000000..d55db82
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/VirtualCompatModeSignInActivity.java
@@ -0,0 +1,120 @@
+/*
+ * 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 com.example.android.autofill.app.edgecases;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.Toast;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+import com.example.android.autofill.app.commoncases.VirtualSignInActivity;
+import com.example.android.autofill.app.view.autofillable.CustomVirtualView;
+import com.example.android.autofill.app.view.autofillable.CustomVirtualViewCompatMode;
+
+/**
+ * Activity that uses a virtual views for Username/Password text fields but doesn't explicitly
+ * implement the Autofill APIs but Accessibility's.
+ *
+ * <p><b>Note:</b> this class is useful to test an Autofill service that supports Compatibility
+ * Mode; real applications with a virtual structure should explicitly support Autofill by
+ * implementing its APIs as {@link VirtualSignInActivity} does.
+
+ * <p>Useful to test an Autofill service that supports Compatibility Mode.
+ *
+ * <p><b>Note: </b>you must whitelist this app's package for compatibility mode. For exmaple, in
+ * a UNIX-like OS such as Linux, you can run:
+ *
+ * <pre>
+ * adb shell settings put global autofill_compat_mode_allowed_packages \
+ * `echo -n com.example.android.autofill.app[custom_virtual_login_header]:; \
+ * adb shell settings get global autofill_compat_mode_allowed_packages`
+ * </pre>
+ */
+public class VirtualCompatModeSignInActivity extends AppCompatActivity {
+
+    private CustomVirtualViewCompatMode mCustomVirtualView;
+    private AutofillManager mAutofillManager;
+    private CustomVirtualView.Line mUsernameLine;
+    private CustomVirtualView.Line mPasswordLine;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.virtual_compat_mode_login_activity);
+
+        mCustomVirtualView = findViewById(R.id.custom_view);
+
+        CustomVirtualView.Partition credentialsPartition =
+                mCustomVirtualView.addPartition(getString(R.string.partition_credentials));
+        mUsernameLine = credentialsPartition.addLine("username", View.AUTOFILL_TYPE_TEXT,
+                getString(R.string.username_label),
+                "         ", false, View.AUTOFILL_HINT_USERNAME);
+        mPasswordLine = credentialsPartition.addLine("password", View.AUTOFILL_TYPE_TEXT,
+                getString(R.string.password_label),
+                "         ", true, View.AUTOFILL_HINT_PASSWORD);
+
+        findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                login();
+            }
+        });
+        findViewById(R.id.clear).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                resetFields();
+                mAutofillManager.cancel();
+            }
+        });
+        mAutofillManager = getSystemService(AutofillManager.class);
+    }
+
+    private void resetFields() {
+        mUsernameLine.reset();
+        mPasswordLine.reset();
+        mCustomVirtualView.postInvalidate();
+    }
+
+    /**
+     * Emulates a login action.
+     */
+    private void login() {
+        String username = mUsernameLine.getText().toString();
+        String password = mPasswordLine.getText().toString();
+        boolean valid = isValidCredentials(username, password);
+        if (valid) {
+            Intent intent = WelcomeActivity
+                    .getStartActivityIntent(VirtualCompatModeSignInActivity.this);
+            startActivity(intent);
+            finish();
+        } else {
+            Toast.makeText(this, "Authentication failed.", Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /**
+     * Dummy implementation for demo purposes. A real service should use secure mechanisms to
+     * authenticate users.
+     */
+    public boolean isValidCredentials(String username, String password) {
+        return username != null && password != null && username.equals(password);
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/AbstractCustomVirtualView.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/AbstractCustomVirtualView.java
new file mode 100644
index 0000000..cb5df83
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/AbstractCustomVirtualView.java
@@ -0,0 +1,433 @@
+/*
+ * 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 com.example.android.autofill.app.view.autofillable;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.autofill.AutofillValue;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.Util;
+import com.google.common.base.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Base class for a custom view that manages its own virtual structure, i.e., this is a leaf
+ * {@link View} in the activity's structure, and it draws its own child UI elements.
+ *
+ * <p>This class only draws the views and provides hooks to integrate them with Android APIs such
+ * as Autofill and Accessibility&mdash;its up to the subclass to implement these integration points.
+ */
+abstract class AbstractCustomVirtualView extends View {
+
+    protected static final boolean DEBUG = true;
+    protected static final boolean VERBOSE = false;
+
+    /**
+     * When set, it notifies AutofillManager of focus change as the view scrolls, so the
+     * autofill UI is continually drawn.
+     * <p>
+     * <p>This is janky and incompatible with the way the autofill UI works on native views, but
+     * it's a cool experiment!
+     */
+    private static final boolean DRAW_AUTOFILL_UI_AFTER_SCROLL = false;
+
+    private static final String TAG = "AbstractCustomVirtualView";
+    private static final int DEFAULT_TEXT_HEIGHT_DP = 34;
+    private static final int VERTICAL_GAP = 10;
+    private static final int UNFOCUSED_COLOR = Color.BLACK;
+    private static final int FOCUSED_COLOR = Color.RED;
+    private static int sNextId;
+    protected final ArrayList<Line> mVirtualViewGroups = new ArrayList<>();
+    protected final SparseArray<Item> mVirtualViews = new SparseArray<>();
+    private final ArrayMap<String, Partition> mPartitionsByName = new ArrayMap<>();
+    protected Line mFocusedLine;
+    protected int mTopMargin;
+    protected int mLeftMargin;
+    private Paint mTextPaint;
+    private int mTextHeight;
+    private int mLineLength;
+
+    protected AbstractCustomVirtualView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mTextPaint = new Paint();
+        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomVirtualView,
+                defStyleAttr, defStyleRes);
+        int defaultHeight =
+                (int) (DEFAULT_TEXT_HEIGHT_DP * getResources().getDisplayMetrics().density);
+        mTextHeight = typedArray.getDimensionPixelSize(
+                R.styleable.CustomVirtualView_internalTextSize, defaultHeight);
+        typedArray.recycle();
+        resetCoordinates();
+    }
+
+    protected Item getItem(int id) {
+        final Item item = mVirtualViews.get(id);
+        Preconditions.checkArgument(item != null, "No item for id %s: %s", id, mVirtualViews);
+        return item;
+    }
+
+    protected void resetCoordinates() {
+        mTextPaint.setStyle(Style.FILL);
+        mTextPaint.setTextSize(mTextHeight);
+        mTopMargin = getPaddingTop();
+        mLeftMargin = getPaddingStart();
+        mLineLength = mTextHeight + VERTICAL_GAP;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (VERBOSE) {
+            Log.v(TAG, "onDraw(): " + mVirtualViewGroups.size() + " lines; canvas:" + canvas);
+        }
+        float x;
+        float y = mTopMargin + mLineLength;
+        for (int i = 0; i < mVirtualViewGroups.size(); i++) {
+            Line line = mVirtualViewGroups.get(i);
+            x = mLeftMargin;
+            if (VERBOSE) Log.v(TAG, "Drawing '" + line + "' at " + x + "x" + y);
+            mTextPaint.setColor(line.mFieldTextItem.focused ? FOCUSED_COLOR : UNFOCUSED_COLOR);
+            String readOnlyText = line.mLabelItem.text + ":  [";
+            String writeText = line.mFieldTextItem.text + "]";
+            // Paints the label first...
+            canvas.drawText(readOnlyText, x, y, mTextPaint);
+            // ...then paints the edit text and sets the proper boundary
+            float deltaX = mTextPaint.measureText(readOnlyText);
+            x += deltaX;
+            line.mBounds.set((int) x, (int) (y - mLineLength),
+                    (int) (x + mTextPaint.measureText(writeText)), (int) y);
+            if (VERBOSE) Log.v(TAG, "setBounds(" + x + ", " + y + "): " + line.mBounds);
+            canvas.drawText(writeText, x, y, mTextPaint);
+            y += mLineLength;
+
+            if (DRAW_AUTOFILL_UI_AFTER_SCROLL) {
+                line.notifyFocusChanged();
+            }
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        int y = (int) event.getY();
+        onMotion(y);
+        return super.onTouchEvent(event);
+    }
+
+    /**
+     * Handles a motion event.
+     *
+     * @param y y coordinate.
+     */
+    protected void onMotion(int y) {
+        if (DEBUG) {
+            Log.d(TAG, "onMotion(): y=" + y + ", range=" + mLineLength + ", top=" + mTopMargin);
+        }
+        int lowerY = mTopMargin;
+        int upperY = -1;
+        for (int i = 0; i < mVirtualViewGroups.size(); i++) {
+            Line line = mVirtualViewGroups.get(i);
+            upperY = lowerY + mLineLength;
+            if (DEBUG) Log.d(TAG, "Line " + i + " ranges from " + lowerY + " to " + upperY);
+            if (lowerY <= y && y <= upperY) {
+                if (mFocusedLine != null) {
+                    Log.d(TAG, "Removing focus from " + mFocusedLine);
+                    mFocusedLine.changeFocus(false);
+                }
+                Log.d(TAG, "Changing focus to " + line);
+                mFocusedLine = line;
+                mFocusedLine.changeFocus(true);
+                invalidate();
+                break;
+            }
+            lowerY += mLineLength;
+        }
+    }
+
+    /**
+     * Creates a new partition with the given name.
+     *
+     * @throws IllegalArgumentException if such partition already exists.
+     */
+    public Partition addPartition(String name) {
+        Preconditions.checkNotNull(name, "Name cannot be null.");
+        Preconditions.checkArgument(!mPartitionsByName.containsKey(name),
+                "Partition with such name already exists.");
+        Partition partition = new Partition(name);
+        mPartitionsByName.put(name, partition);
+        return partition;
+    }
+
+
+    protected abstract void notifyFocusGained(int virtualId, Rect bounds);
+
+    protected abstract void notifyFocusLost(int virtualId);
+
+    protected void onLineAdded(int id, Partition partition) {
+        if (VERBOSE) Log.v(TAG, "onLineAdded: id=" + id + ", partition=" + partition);
+    }
+
+    protected void showError(String message) {
+        showMessage(true, message);
+    }
+
+    protected void showMessage(String message) {
+        showMessage(false, message);
+    }
+
+    private void showMessage(boolean warning, String message) {
+        if (warning) {
+            Log.w(TAG, message);
+        } else {
+            Log.i(TAG, message);
+        }
+        Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show();
+    }
+
+    protected static final class Item {
+        public final int id;
+        public final String idEntry;
+        public final Line line;
+        public final boolean editable;
+        public final boolean sanitized;
+        public final String[] hints;
+        public final int type;
+        public CharSequence text;
+        public boolean focused = false;
+        public long date;
+        private TextWatcher mListener;
+
+        Item(Line line, int id, String idEntry, String[] hints, int type, CharSequence text,
+                boolean editable, boolean sanitized) {
+            this.line = line;
+            this.id = id;
+            this.idEntry = idEntry;
+            this.text = text;
+            this.editable = editable;
+            this.sanitized = sanitized;
+            this.hints = hints;
+            this.type = type;
+        }
+
+        @Override
+        public String toString() {
+            return id + "/" + idEntry + ": "
+                    + (type == AUTOFILL_TYPE_DATE ? date : text) // TODO: use DateFormat for date
+                    + " (" + Util.getAutofillTypeAsString(type) + ")"
+                    + (editable ? " (editable)" : " (read-only)"
+                    + (sanitized ? " (sanitized)" : " (sensitive"))
+                    + (hints == null ? " (no hints)" : " ( " + Arrays.toString(hints) + ")");
+        }
+
+        protected String getClassName() {
+            return editable ? EditText.class.getName() : TextView.class.getName();
+        }
+
+        protected AutofillValue getAutofillValue() {
+            switch (type) {
+                case AUTOFILL_TYPE_TEXT:
+                    return (TextUtils.getTrimmedLength(text) > 0)
+                            ? AutofillValue.forText(text)
+                            : null;
+                case AUTOFILL_TYPE_DATE:
+                    return AutofillValue.forDate(date);
+                default:
+                    return null;
+            }
+        }
+
+        protected AccessibilityNodeInfo provideAccessibilityNodeInfo(View parent, Context context) {
+            final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+            node.setSource(parent, id);
+            node.setPackageName(context.getPackageName());
+            node.setClassName(getClassName());
+            node.setEditable(editable);
+            node.setViewIdResourceName(idEntry);
+            node.setVisibleToUser(true);
+            final Rect absBounds = line.getAbsCoordinates();
+            if (absBounds != null) {
+                node.setBoundsInScreen(absBounds);
+            }
+            if (TextUtils.getTrimmedLength(text) > 0) {
+                // TODO: Must checked trimmed length because input fields use 8 empty spaces to
+                // set width
+                node.setText(text);
+            }
+            return node;
+        }
+
+        protected void setText(CharSequence value) {
+            if (!editable) {
+                Log.w(TAG, "Item for id " + id + " is not editable: " + this);
+                return;
+            }
+            text = value;
+            if (mListener != null) {
+                Log.d(TAG, "Notify listener: " + text);
+                mListener.onTextChanged(text, 0, 0, 0);
+            }
+        }
+
+    }
+
+    /**
+     * A partition represents a logical group of items, such as credit card info.
+     */
+    public final class Partition {
+        protected final String mName;
+        protected final SparseArray<Line> mLines = new SparseArray<>();
+
+        private Partition(String name) {
+            mName = name;
+        }
+
+        /**
+         * Adds a new line (containining a label and an input field) to the view.
+         *
+         * @param idEntryPrefix id prefix used to identify the line - label node will be suffixed
+         *                      with {@code Label} and editable node with {@code Field}.
+         * @param autofillType  {@link View#getAutofillType() autofill type} of the field.
+         * @param label         text used in the label.
+         * @param text          initial text used in the input field.
+         * @param sensitive     whether the input is considered sensitive.
+         * @param autofillHints list of autofill hints.
+         * @return the new line.
+         */
+        public Line addLine(String idEntryPrefix, int autofillType, String label, String text,
+                boolean sensitive, String... autofillHints) {
+            Preconditions.checkArgument(autofillType == AUTOFILL_TYPE_TEXT
+                    || autofillType == AUTOFILL_TYPE_DATE, "Unsupported type: " + autofillType);
+            Line line = new Line(idEntryPrefix, autofillType, label, autofillHints, text,
+                    !sensitive);
+            mVirtualViewGroups.add(line);
+            int id = line.mFieldTextItem.id;
+            mLines.put(id, line);
+            mVirtualViews.put(line.mLabelItem.id, line.mLabelItem);
+            mVirtualViews.put(id, line.mFieldTextItem);
+            onLineAdded(id, this);
+
+            return line;
+        }
+
+        /**
+         * Resets the value of all items in the partition.
+         */
+        public void reset() {
+            for (int i = 0; i < mLines.size(); i++) {
+                mLines.valueAt(i).reset();
+            }
+        }
+
+        @Override
+        public String toString() {
+            return mName;
+        }
+    }
+
+    /**
+     * A line in the virtual view contains a label and an input field.
+     */
+    public final class Line {
+
+        protected final Item mFieldTextItem;
+        // Boundaries of the text field, relative to the CustomView
+        protected final Rect mBounds = new Rect();
+        protected final Item mLabelItem;
+        protected final int mAutofillType;
+
+        private Line(String idEntryPrefix, int autofillType, String label, String[] hints,
+                String text, boolean sanitized) {
+            this.mAutofillType = autofillType;
+            this.mLabelItem = new Item(this, ++sNextId, idEntryPrefix + "Label", null,
+                    AUTOFILL_TYPE_NONE, label, false, true);
+            this.mFieldTextItem = new Item(this, ++sNextId, idEntryPrefix + "Field", hints,
+                    autofillType, text, true, sanitized);
+        }
+
+        private void changeFocus(boolean focused) {
+            mFieldTextItem.focused = focused;
+            notifyFocusChanged();
+        }
+
+        void notifyFocusChanged() {
+            if (mFieldTextItem.focused) {
+                Rect absBounds = getAbsCoordinates();
+                if (DEBUG) {
+                    Log.d(TAG, "focus gained on " + mFieldTextItem.id + "; absBounds=" + absBounds);
+                }
+                notifyFocusGained(mFieldTextItem.id, absBounds);
+            } else {
+                if (DEBUG) Log.d(TAG, "focus lost on " + mFieldTextItem.id);
+                notifyFocusLost(mFieldTextItem.id);
+            }
+        }
+
+        private Rect getAbsCoordinates() {
+            // Must offset the boundaries so they're relative to the CustomView.
+            int[] offset = new int[2];
+            getLocationOnScreen(offset);
+            Rect absBounds = new Rect(mBounds.left + offset[0],
+                    mBounds.top + offset[1],
+                    mBounds.right + offset[0], mBounds.bottom + offset[1]);
+            if (VERBOSE) {
+                Log.v(TAG, "getAbsCoordinates() for " + mFieldTextItem.id + ": bounds=" + mBounds
+                        + " offset: " + Arrays.toString(offset) + " absBounds: " + absBounds);
+            }
+            return absBounds;
+        }
+
+        /**
+         * Gets the value of the input field text.
+         */
+        public CharSequence getText() {
+            return mFieldTextItem.text;
+        }
+
+        /**
+         * Resets the value of the input field text.
+         */
+        public void reset() {
+            mFieldTextItem.text = "        ";
+        }
+
+        @Override
+        public String toString() {
+            return "Label: " + mLabelItem + " Text: " + mFieldTextItem
+                    + " Focused: " + mFieldTextItem.focused + " Type: " + mAutofillType;
+        }
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CreditCardExpirationDateCompoundView.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CreditCardExpirationDateCompoundView.java
new file mode 100644
index 0000000..1cee75a
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CreditCardExpirationDateCompoundView.java
@@ -0,0 +1,135 @@
+/*
+ * 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 com.example.android.autofill.app.view.autofillable;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.FrameLayout;
+import android.widget.Spinner;
+
+import com.example.android.autofill.app.R;
+
+import java.util.Calendar;
+
+import static com.example.android.autofill.app.Util.TAG;
+
+/**
+ * A custom view that represents a {@link View#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE} using
+ * 2 {@link Spinner spinners} to represent the credit card expiration month and year.
+ */
+public class CreditCardExpirationDateCompoundView extends FrameLayout {
+
+    private static final int CC_EXP_YEARS_COUNT = 5;
+
+    private final String[] mYears = new String[CC_EXP_YEARS_COUNT];
+
+    private Spinner mCcExpMonthSpinner;
+    private Spinner mCcExpYearSpinner;
+
+    public CreditCardExpirationDateCompoundView(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public CreditCardExpirationDateCompoundView(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CreditCardExpirationDateCompoundView(@NonNull Context context,
+            @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public CreditCardExpirationDateCompoundView(@NonNull final Context context,
+            @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        View rootView = LayoutInflater.from(context).inflate(R.layout.cc_exp_date, this);
+        mCcExpMonthSpinner = rootView.findViewById(R.id.ccExpMonth);
+        mCcExpYearSpinner = rootView.findViewById(R.id.ccExpYear);
+        setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS);
+        ArrayAdapter<CharSequence> monthAdapter = ArrayAdapter.createFromResource
+                (context, R.array.month_array, android.R.layout.simple_spinner_item);
+        monthAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        mCcExpMonthSpinner.setAdapter(monthAdapter);
+        int year = Calendar.getInstance().get(Calendar.YEAR);
+        for (int i = 0; i < mYears.length; i++) {
+            mYears[i] = Integer.toString(year + i);
+        }
+        mCcExpYearSpinner.setAdapter(new ArrayAdapter<>(context,
+                android.R.layout.simple_spinner_item, mYears));
+        AdapterView.OnItemSelectedListener onItemSelectedListener =
+                new AdapterView.OnItemSelectedListener() {
+                    @Override
+                    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+                        context.getSystemService(AutofillManager.class)
+                                .notifyValueChanged(CreditCardExpirationDateCompoundView.this);
+                    }
+
+                    @Override
+                    public void onNothingSelected(AdapterView<?> parent) {
+                    }
+                };
+        mCcExpMonthSpinner.setOnItemSelectedListener(onItemSelectedListener);
+        mCcExpYearSpinner.setOnItemSelectedListener(onItemSelectedListener);
+    }
+
+    @Override
+    public AutofillValue getAutofillValue() {
+        Calendar calendar = Calendar.getInstance();
+        // Set hours, minutes, seconds, and millis to 0 to ensure getAutofillValue() == the value
+        // set by autofill(). Without this line, the view will not turn yellow when updated.
+        calendar.clear();
+        int year = Integer.parseInt(mCcExpYearSpinner.getSelectedItem().toString());
+        int month = mCcExpMonthSpinner.getSelectedItemPosition();
+        calendar.set(Calendar.YEAR, year);
+        calendar.set(Calendar.MONTH, month);
+        long unixTime = calendar.getTimeInMillis();
+        return AutofillValue.forDate(unixTime);
+    }
+
+    @Override
+    public void autofill(AutofillValue value) {
+        if (!value.isDate()) {
+            Log.w(TAG, "Ignoring autofill() because service sent a non-date value:" + value);
+            return;
+        }
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeInMillis(value.getDateValue());
+        int month = calendar.get(Calendar.MONTH);
+        int year = calendar.get(Calendar.YEAR);
+        mCcExpMonthSpinner.setSelection(month);
+        mCcExpYearSpinner.setSelection(year - Integer.parseInt(mYears[0]));
+    }
+
+    @Override
+    public int getAutofillType() {
+        return AUTOFILL_TYPE_DATE;
+    }
+
+    public void reset() {
+        mCcExpMonthSpinner.setSelection(0);
+        mCcExpYearSpinner.setSelection(0);
+    }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CreditCardExpirationDatePickerView.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CreditCardExpirationDatePickerView.java
new file mode 100644
index 0000000..870825a
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CreditCardExpirationDatePickerView.java
@@ -0,0 +1,163 @@
+/*
+ * 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 com.example.android.autofill.app.view.autofillable;
+
+import android.app.DatePickerDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v7.widget.AppCompatEditText;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillValue;
+import android.widget.DatePicker;
+
+import com.example.android.autofill.app.R;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import static com.example.android.autofill.app.Util.DEBUG;
+import static com.example.android.autofill.app.Util.TAG;
+
+/**
+ * A custom view that represents a {@link View#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE} using
+ * a non-editable {@link EditText} that triggers a {@link DatePickerDialog} to represent the
+ * credit card expiration month and year.
+ */
+public class CreditCardExpirationDatePickerView extends AppCompatEditText {
+
+    private static final int CC_EXP_YEARS_COUNT = 5;
+
+    /**
+     * Calendar instance used for month / year calculations. Should be reset before each use.
+     */
+    private final Calendar mTempCalendar;
+
+    private int mMonth;
+    private int mYear;
+
+    public CreditCardExpirationDatePickerView(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public CreditCardExpirationDatePickerView(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CreditCardExpirationDatePickerView(@NonNull Context context,
+            @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        // Use the current date as the initial date in the picker.
+        mTempCalendar = Calendar.getInstance();
+        mYear = mTempCalendar.get(Calendar.YEAR);
+        mMonth = mTempCalendar.get(Calendar.MONTH);
+    }
+
+    /**
+     * Gets a temporary calendar set with the View's year and month.
+     */
+    private Calendar getCalendar() {
+        mTempCalendar.clear();
+        mTempCalendar.set(Calendar.YEAR, mYear);
+        mTempCalendar.set(Calendar.MONTH, mMonth);
+        mTempCalendar.set(Calendar.DATE, 1);
+        return mTempCalendar;
+    }
+
+    @Override
+    public AutofillValue getAutofillValue() {
+        Calendar c = getCalendar();
+        AutofillValue value = AutofillValue.forDate(c.getTimeInMillis());
+        if (DEBUG) Log.d(TAG, "getAutofillValue(): " + value);
+        return value;
+    }
+
+    @Override
+    public void autofill(AutofillValue value) {
+        if (value == null || !value.isDate()) {
+            Log.w(TAG, "autofill(): invalid value " + value);
+            return;
+        }
+        long time = value.getDateValue();
+        mTempCalendar.setTimeInMillis(time);
+        int year = mTempCalendar.get(Calendar.YEAR);
+        int month = mTempCalendar.get(Calendar.MONTH);
+        if (DEBUG) Log.d(TAG, "autofill(" + value + "): " + month + "/" + year);
+        setDate(year, month);
+    }
+
+    private void setDate(int year, int month) {
+        mYear = year;
+        mMonth = month;
+        Date selectedDate = new Date(getCalendar().getTimeInMillis());
+        String dateString = DateFormat.getDateFormat(getContext()).format(selectedDate);
+        setText(dateString);
+    }
+
+    @Override
+    public int getAutofillType() {
+        return AUTOFILL_TYPE_DATE;
+    }
+
+    public void reset() {
+        mTempCalendar.setTimeInMillis(System.currentTimeMillis());
+        setDate(mTempCalendar.get(Calendar.YEAR), mTempCalendar.get(Calendar.MONTH));
+    }
+
+    public void showDatePickerDialog(FragmentManager fragmentManager) {
+        DatePickerFragment newFragment = new DatePickerFragment();
+        newFragment.mParent = this;
+        newFragment.show(fragmentManager, "datePicker");
+    }
+
+    public static class DatePickerFragment extends DialogFragment
+            implements DatePickerDialog.OnDateSetListener {
+
+        private CreditCardExpirationDatePickerView mParent;
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            DatePickerDialog dialog = new DatePickerDialog(getActivity(),
+                    R.style.CustomDatePickerDialogTheme, this, mParent.mYear, mParent.mMonth, 1);
+
+            DatePicker datePicker = dialog.getDatePicker();
+
+            // Limit range.
+            Calendar c = mParent.getCalendar();
+            datePicker.setMinDate(c.getTimeInMillis());
+            c.set(Calendar.YEAR, mParent.mYear + CC_EXP_YEARS_COUNT - 1);
+            datePicker.setMaxDate(c.getTimeInMillis());
+
+            // Remove day.
+            datePicker.findViewById(getResources().getIdentifier("day", "id", "android"))
+                    .setVisibility(View.GONE);
+            return dialog;
+        }
+
+        @Override
+        public void onDateSet(DatePicker view, int year, int month, int day) {
+            mParent.setDate(year, month);
+        }
+    }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CustomVirtualView.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CustomVirtualView.java
new file mode 100644
index 0000000..67c8661
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CustomVirtualView.java
@@ -0,0 +1,189 @@
+/*
+ * 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 com.example.android.autofill.app.view.autofillable;
+
+import static com.example.android.autofill.app.Util.bundleToString;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewStructure;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.Util;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+/**
+ * A custom View with a virtual structure that implements the Autofill APIs.
+ */
+public class CustomVirtualView extends AbstractCustomVirtualView {
+
+    private static final String TAG = "CustomView";
+
+    protected final AutofillManager mAutofillManager;
+    private final SparseArray<Partition> mPartitionsByAutofillId = new SparseArray<>();
+
+    public CustomVirtualView(Context context) {
+        this(context, null);
+    }
+
+    public CustomVirtualView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CustomVirtualView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public CustomVirtualView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mAutofillManager = context.getSystemService(AutofillManager.class);
+    }
+
+    @Override
+    public void autofill(SparseArray<AutofillValue> values) {
+        Context context = getContext();
+
+        // User has just selected a Dataset from the list of autofill suggestions.
+        // The Dataset is comprised of a list of AutofillValues, with each AutofillValue meant
+        // to fill a specific autofillable view. Now we have to update the UI based on the
+        // AutofillValues in the list, but first we make sure all autofilled values belong to the
+        // same partition
+        if (DEBUG) {
+            Log.d(TAG, "autofill(): " + values);
+        }
+
+        // First get the name of all partitions in the values
+        ArraySet<String> partitions = new ArraySet<>();
+        for (int i = 0; i < values.size(); i++) {
+            int id = values.keyAt(i);
+            Partition partition = mPartitionsByAutofillId.get(id);
+            if (partition == null) {
+                showError(context.getString(R.string.message_autofill_no_partitions, id,
+                        mPartitionsByAutofillId));
+                return;
+            }
+            partitions.add(partition.mName);
+        }
+
+        // Then make sure they follow the Highlander rule (There can be only one)
+        if (partitions.size() != 1) {
+            showError(context.getString(R.string.message_autofill_blocked, partitions));
+            return;
+        }
+
+        // Finally, autofill it.
+        DateFormat df = android.text.format.DateFormat.getDateFormat(context);
+        for (int i = 0; i < values.size(); i++) {
+            int id = values.keyAt(i);
+            AutofillValue value = values.valueAt(i);
+            Item item = mVirtualViews.get(id);
+
+            if (item == null) {
+                Log.w(TAG, "No item for id " + id);
+                continue;
+            }
+
+            if (!item.editable) {
+                showError(context.getString(R.string.message_autofill_readonly, item.text));
+                continue;
+            }
+
+            // Check if the type was properly set by the autofill service
+            if (DEBUG) {
+                Log.d(TAG, "Validating " + i
+                        + ": expectedType=" + Util.getAutofillTypeAsString(item.type)
+                        + "(" + item.type + "), value=" + value);
+            }
+            boolean valid = false;
+            if (value.isText() && item.type == AUTOFILL_TYPE_TEXT) {
+                item.text = value.getTextValue();
+                valid = true;
+            } else if (value.isDate() && item.type == AUTOFILL_TYPE_DATE) {
+                item.text = df.format(new Date(value.getDateValue()));
+                valid = true;
+            } else {
+                Log.w(TAG, "Unsupported type: " + value);
+            }
+            if (!valid) {
+                item.text = context.getString(R.string.message_autofill_invalid);
+            }
+        }
+        postInvalidate();
+        showMessage(context.getString(R.string.message_autofill_ok, partitions.valueAt(0)));
+    }
+
+    @Override
+    public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
+        // Build a ViewStructure that will get passed to the AutofillService by the framework
+        // when it is time to find autofill suggestions.
+        structure.setClassName(getClass().getName());
+        int childrenSize = mVirtualViews.size();
+        if (DEBUG) {
+            Log.d(TAG, "onProvideAutofillVirtualStructure(): flags = " + flags + ", items = "
+                    + childrenSize + ", extras: " + bundleToString(structure.getExtras()));
+        }
+        int index = structure.addChildCount(childrenSize);
+        // Traverse through the view hierarchy, including virtual child views. For each view, we
+        // need to set the relevant autofill metadata and add it to the ViewStructure.
+        for (int i = 0; i < childrenSize; i++) {
+            Item item = mVirtualViews.valueAt(i);
+            if (DEBUG) {
+                Log.d(TAG, "Adding new child at index " + index + ": " + item);
+            }
+            ViewStructure child = structure.newChild(index);
+            child.setAutofillId(structure.getAutofillId(), item.id);
+            child.setAutofillHints(item.hints);
+            child.setAutofillType(item.type);
+            child.setAutofillValue(item.getAutofillValue());
+            child.setDataIsSensitive(!item.sanitized);
+            child.setFocused(item.focused);
+            child.setVisibility(View.VISIBLE);
+            child.setDimens(item.line.mBounds.left, item.line.mBounds.top, 0, 0,
+                    item.line.mBounds.width(), item.line.mBounds.height());
+            child.setId(item.id, getContext().getPackageName(), null, item.idEntry);
+            child.setClassName(item.getClassName());
+            child.setDimens(item.line.mBounds.left, item.line.mBounds.top, 0, 0,
+                    item.line.mBounds.width(), item.line.mBounds.height());
+            index++;
+        }
+    }
+
+    @Override
+    protected void notifyFocusGained(int virtualId, Rect bounds) {
+        mAutofillManager.notifyViewEntered(this, virtualId, bounds);
+    }
+
+    @Override
+    protected void notifyFocusLost(int virtualId) {
+        mAutofillManager.notifyViewExited(this, virtualId);
+    }
+
+    @Override
+    protected void onLineAdded(int id, Partition partition) {
+        mPartitionsByAutofillId.put(id, partition);
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CustomVirtualViewCompatMode.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CustomVirtualViewCompatMode.java
new file mode 100644
index 0000000..7dfb6af
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CustomVirtualViewCompatMode.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 com.example.android.autofill.app.view.autofillable;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+
+/**
+ * A custom View with a virtual structure that implements the Accessibility APIs.
+ *
+ * <p><b>Note:</b> this class is useful to test an Autofill service that supports Compatibility
+ * Mode; real applications with a virtual structure should explicitly support Autofill by
+ * implementing its APIs as {@link CustomVirtualView} does.
+ */
+public class CustomVirtualViewCompatMode extends AbstractCustomVirtualView {
+
+    private static final String TAG = "CustomVirtualViewCompatMode";
+
+    private final AccessibilityDelegate mAccessibilityDelegate;
+    private final AccessibilityNodeProvider mAccessibilityNodeProvider;
+
+    public CustomVirtualViewCompatMode(Context context) {
+        this(context, null);
+    }
+
+    public CustomVirtualViewCompatMode(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CustomVirtualViewCompatMode(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public CustomVirtualViewCompatMode(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        mAccessibilityNodeProvider = new AccessibilityNodeProvider() {
+            @Override
+            public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+                if (DEBUG) {
+                    Log.d(TAG, "createAccessibilityNodeInfo(): id=" + virtualViewId);
+                }
+                switch (virtualViewId) {
+                    case AccessibilityNodeProvider.HOST_VIEW_ID:
+                        return onProvideAutofillCompatModeAccessibilityNodeInfo();
+                    default:
+                        final Item item = getItem(virtualViewId);
+                        return item.provideAccessibilityNodeInfo(CustomVirtualViewCompatMode.this,
+                                getContext());
+                }
+            }
+
+            @Override
+            public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+                if (action == AccessibilityNodeInfo.ACTION_SET_TEXT) {
+                    final CharSequence text = arguments.getCharSequence(
+                            AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE);
+                    final Item item = getItem(virtualViewId);
+                    item.setText(text);
+                    invalidate();
+                    return true;
+                }
+
+                return false;
+            }
+        };
+        mAccessibilityDelegate = new AccessibilityDelegate() {
+            @Override
+            public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+                return mAccessibilityNodeProvider;
+            }
+        };
+        setAccessibilityDelegate(mAccessibilityDelegate);
+    }
+
+    @Override
+    protected void notifyFocusGained(int virtualId, Rect bounds) {
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED, virtualId);
+    }
+
+    @Override
+    protected void notifyFocusLost(int virtualId) {
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED, virtualId);
+    }
+
+    private void sendAccessibilityEvent(int eventType, int virtualId) {
+        AccessibilityEvent event = AccessibilityEvent.obtain();
+        event.setEventType(eventType);
+        event.setSource(this, virtualId);
+        event.setEnabled(true);
+        event.setPackageName(getContext().getPackageName());
+        if (VERBOSE) {
+            Log.v(TAG, "sendAccessibilityEvent(" + eventType + ", " + virtualId + "): " + event);
+        }
+        getContext().getSystemService(AccessibilityManager.class).sendAccessibilityEvent(event);
+    }
+
+    private AccessibilityNodeInfo onProvideAutofillCompatModeAccessibilityNodeInfo() {
+        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+
+        final String packageName = getContext().getPackageName();
+        node.setPackageName(packageName);
+        node.setClassName(getClass().getName());
+
+        final int childrenSize = mVirtualViews.size();
+        for (int i = 0; i < childrenSize; i++) {
+            final Item item = mVirtualViews.valueAt(i);
+            if (DEBUG) {
+                Log.d(TAG, "Adding new A11Y child with id " + item.id + ": " + item);
+            }
+            node.addChild(this, item.id);
+        }
+        return node;
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/ScrollableCustomVirtualView.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/ScrollableCustomVirtualView.java
new file mode 100644
index 0000000..cdc9d98
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/ScrollableCustomVirtualView.java
@@ -0,0 +1,105 @@
+/*
+ * 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 com.example.android.autofill.app.view.autofillable;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+/**
+ * A version of {@link CustomVirtualView} that uses gesture to provide scrolling.
+ */
+public class ScrollableCustomVirtualView extends CustomVirtualView
+        implements GestureDetector.OnGestureListener {
+
+    private static final String TAG = "ScrollableCustomView";
+
+    private GestureDetector mGestureDetector;
+
+    public ScrollableCustomVirtualView(Context context) {
+        this(context, null);
+    }
+
+    public ScrollableCustomVirtualView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+
+    }
+
+    public ScrollableCustomVirtualView(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public ScrollableCustomVirtualView(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mGestureDetector = new GestureDetector(context, this);
+    }
+
+    /**
+     * Resets the UI to the intial state.
+     */
+    public void resetPositions() {
+        super.resetCoordinates();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        return mGestureDetector.onTouchEvent(event);
+    }
+
+    /*
+     * Methods below implement GestureDetector.OnGestureListener
+     */
+    @Override
+    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+        if (VERBOSE) Log.v(TAG, "onScroll(): " + distanceX + " - " + distanceY);
+        if (mFocusedLine != null) {
+            mAutofillManager.notifyViewExited(this, mFocusedLine.mFieldTextItem.id);
+        }
+        mTopMargin -= distanceY;
+        mLeftMargin -= distanceX;
+        invalidate();
+        return true;
+    }
+
+    @Override
+    public boolean onDown(MotionEvent event) {
+        onMotion((int) event.getY());
+        return true;
+    }
+
+    @Override
+    public void onShowPress(MotionEvent e) {
+    }
+
+    @Override
+    public boolean onSingleTapUp(MotionEvent e) {
+        return true;
+    }
+
+    @Override
+    public void onLongPress(MotionEvent e) {
+    }
+
+    @Override
+    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/widget/InfoButton.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/widget/InfoButton.java
new file mode 100644
index 0000000..92a221f
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/widget/InfoButton.java
@@ -0,0 +1,54 @@
+/*
+ * 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 com.example.android.autofill.app.view.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.widget.AppCompatImageButton;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.example.android.autofill.app.R;
+
+public class InfoButton extends AppCompatImageButton {
+    public InfoButton(Context context) {
+        this(context, null);
+    }
+
+    public InfoButton(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public InfoButton(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.InfoButton,
+                defStyleAttr, 0);
+        String infoText = typedArray.getString(R.styleable.InfoButton_dialogText);
+        typedArray.recycle();
+        setInfoText(infoText);
+    }
+
+    public void setInfoText(final String infoText) {
+        setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                new AlertDialog.Builder(InfoButton.this.getContext())
+                        .setMessage(infoText).create().show();
+            }
+        });
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/widget/NavigationItem.java b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/widget/NavigationItem.java
new file mode 100644
index 0000000..1647277
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/widget/NavigationItem.java
@@ -0,0 +1,98 @@
+/*
+ * 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 com.example.android.autofill.app.view.widget;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.ColorRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.os.BuildCompat;
+import android.support.v7.widget.CardView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.example.android.autofill.app.R;
+
+import static com.example.android.autofill.app.Util.TAG;
+
+public class NavigationItem extends FrameLayout {
+    public NavigationItem(Context context) {
+        this(context, null);
+    }
+
+    public NavigationItem(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NavigationItem(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public NavigationItem(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.NavigationItem,
+                defStyleAttr, defStyleRes);
+        int activityMinSdk = typedArray.getInteger(R.styleable.NavigationItem_minSdk, 26);
+
+        // TODO: Remove BuildCompat.isAtLeastP() check when API 28 is finalized.
+        int deviceMinSdk = BuildCompat.isAtLeastP() ? 28 : Build.VERSION.SDK_INT;
+        if (deviceMinSdk < activityMinSdk) {
+            // If device's SDK is lower than the minSdk specified by the NavigationItem, hide it.
+            setVisibility(View.GONE);
+            return;
+        }
+        String labelText = typedArray.getString(R.styleable.NavigationItem_labelText);
+        String infoText = typedArray.getString(R.styleable.NavigationItem_infoText);
+        Drawable logoDrawable = typedArray.getDrawable(R.styleable.NavigationItem_itemLogo);
+        @ColorRes int colorRes = typedArray.getResourceId(R.styleable.NavigationItem_imageColor, 0);
+        String launchingActivityName = typedArray.getString(R.styleable.NavigationItem_destinationActivityName);
+        int imageColor = ContextCompat.getColor(getContext(), colorRes);
+        typedArray.recycle();
+        View rootView = LayoutInflater.from(context).inflate(R.layout.navigation_item, this);
+        TextView buttonLabel = rootView.findViewById(R.id.buttonLabel);
+        buttonLabel.setText(labelText);
+        if (logoDrawable != null) {
+            Drawable mutatedLogoDrawable = logoDrawable.mutate();
+            mutatedLogoDrawable.setColorFilter(imageColor, PorterDuff.Mode.SRC_IN);
+            buttonLabel.setCompoundDrawablesRelativeWithIntrinsicBounds(mutatedLogoDrawable, null,
+                    null, null);
+        }
+        InfoButton infoButton = rootView.findViewById(R.id.infoButton);
+        infoButton.setInfoText(infoText);
+        infoButton.setColorFilter(imageColor);
+        CardView outerView = rootView.findViewById(R.id.cardView);
+        outerView.setOnClickListener((view) -> {
+            if (launchingActivityName != null) {
+                Intent intent = new Intent();
+                intent.setClassName(getContext().getPackageName(), launchingActivityName);
+                context.startActivity(intent);
+            } else {
+                Log.w(TAG, "Launching Activity name not set.");
+            }
+        });
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/res/drawable/ic_disabled_black_24dp.xml b/prebuilts/gradle/AutofillFramework/Application/src/main/res/drawable/ic_disabled_black_24dp.xml
new file mode 100644
index 0000000..60e4cbd
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/res/drawable/ic_disabled_black_24dp.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10 10,-4.5 10,-10S17.5,2 12,2zM4,12c0,-4.4 3.6,-8 8,-8 1.8,0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4,13.8 4,12zM12,20c-1.8,0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20,10.2 20,12c0,4.4 -3.6,8 -8,8z"/>
+</vector>
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/res/drawable/ic_format_list_bulleted_black_24dp.xml b/prebuilts/gradle/AutofillFramework/Application/src/main/res/drawable/ic_format_list_bulleted_black_24dp.xml
new file mode 100644
index 0000000..b53c0be
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/res/drawable/ic_format_list_bulleted_black_24dp.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M4,10.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM4,4.5c-0.83,0 -1.5,0.67 -1.5,1.5S3.17,7.5 4,7.5 5.5,6.83 5.5,6 4.83,4.5 4,4.5zM4,16.5c-0.83,0 -1.5,0.68 -1.5,1.5s0.68,1.5 1.5,1.5 1.5,-0.68 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM7,19h14v-2L7,17v2zM7,13h14v-2L7,11v2zM7,5v2h14L21,5L7,5z"/>
+</vector>
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/fragment_common_cases.xml b/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/fragment_common_cases.xml
new file mode 100644
index 0000000..5b2223c
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/fragment_common_cases.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingBottom="@dimen/activity_vertical_margin"
+        android:paddingEnd="@dimen/activity_horizontal_margin"
+        android:paddingStart="@dimen/activity_horizontal_margin"
+        android:paddingTop="@dimen/activity_vertical_margin">
+
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/standardViewSignInButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_red_dark"
+            app:infoText="@string/edittext_login_info"
+            app:itemLogo="@drawable/ic_edittexts_logo_24dp"
+            app:labelText="@string/navigation_button_edittext_login_label"
+            app:destinationActivityName="com.example.android.autofill.app.commoncases.StandardSignInActivity" />
+
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/virtualViewSignInButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_green_dark"
+            app:infoText="@string/custom_virtual_login_info"
+            app:itemLogo="@drawable/ic_custom_virtual_logo_24dp"
+            app:labelText="@string/navigation_button_custom_virtual_view_login_label"
+            app:destinationActivityName="com.example.android.autofill.app.commoncases.VirtualSignInActivity" />
+
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/creditCardSpinnersButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_orange_dark"
+            app:infoText="@string/spinners_credit_card_info"
+            app:itemLogo="@drawable/ic_spinners_logo_24dp"
+            app:labelText="@string/navigation_button_spinners_credit_card_label"
+            app:destinationActivityName="com.example.android.autofill.app.commoncases.CreditCardSpinnersActivity" />
+
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/webviewSignInButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_purple"
+            app:infoText="@string/webview_login_info"
+            app:itemLogo="@drawable/ic_web_black_24dp"
+            app:labelText="@string/navigation_button_web_view_login_label"
+            app:destinationActivityName="com.example.android.autofill.app.commoncases.WebViewSignInActivity" />
+
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/creditCardCompoundViewButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_blue_light"
+            app:infoText="@string/compound_view_credit_card_info"
+            app:itemLogo="@drawable/ic_view_module_black_24dp"
+            app:labelText="@string/navigation_button_compound_view_credit_card_label"
+            app:destinationActivityName="com.example.android.autofill.app.commoncases.CreditCardCompoundViewActivity" />
+
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/emailComposeButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_red_dark"
+            app:infoText="@string/email_compose_info"
+            app:itemLogo="@drawable/ic_email_black_24dp"
+            app:labelText="@string/navigation_button_email_compose_label"
+            app:destinationActivityName="com.example.android.autofill.app.commoncases.EmailComposeActivity"/>
+
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/creditCardDatePickerButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_green_light"
+            app:infoText="@string/date_picker_credit_card_info"
+            app:itemLogo="@drawable/ic_view_module_black_24dp"
+            app:labelText="@string/navigation_button_date_picker_credit_card_label"
+            app:destinationActivityName="com.example.android.autofill.app.commoncases.CreditCardDatePickerActivity" />
+
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/recyclerViewButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_orange_light"
+            app:infoText="@string/recycler_view_info"
+            app:itemLogo="@drawable/ic_format_list_bulleted_black_24dp"
+            app:labelText="@string/navigation_button_recycler_view_label"
+            app:destinationActivityName="com.example.android.autofill.app.commoncases.RecyclerViewActivity"
+            app:minSdk="28"/>
+    </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/fragment_edge_cases.xml b/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/fragment_edge_cases.xml
new file mode 100644
index 0000000..88a353d
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/fragment_edge_cases.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingBottom="@dimen/activity_vertical_margin"
+        android:paddingEnd="@dimen/activity_horizontal_margin"
+        android:paddingStart="@dimen/activity_horizontal_margin"
+        android:paddingTop="@dimen/activity_vertical_margin">
+
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/standardLoginWithAutoCompleteButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_blue_dark"
+            app:infoText="@string/autocomplete_login_info"
+            app:itemLogo="@drawable/ic_autocomplete_logo_24dp"
+            app:labelText="@string/navigation_button_autocomplete_login_label"
+            app:destinationActivityName="com.example.android.autofill.app.commoncases.StandardAutoCompleteSignInActivity"/>
+
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/virtualViewSignInButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_green_dark"
+            app:infoText="@string/custom_virtual_compat_mode_login_info"
+            app:itemLogo="@drawable/ic_custom_virtual_logo_24dp"
+            app:labelText="@string/navigation_button_custom_virtual_view_compat_mode_login_label"
+            app:destinationActivityName="com.example.android.autofill.app.edgecases.VirtualCompatModeSignInActivity" />
+
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/multiplePartitionsButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_green_dark"
+            app:infoText="@string/multiple_partitions"
+            app:itemLogo="@drawable/ic_custom_virtual_logo_24dp"
+            app:labelText="@string/navigation_button_multiple_partitions_label"
+            app:destinationActivityName="com.example.android.autofill.app.edgecases.MultiplePartitionsActivity"/>
+
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/creditCardButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_orange_dark"
+            app:infoText="@string/credit_card_info"
+            app:itemLogo="@drawable/ic_edittexts_logo_24dp"
+            app:labelText="@string/navigation_button_credit_card_label"
+            app:destinationActivityName="com.example.android.autofill.app.edgecases.CreditCardActivity"/>
+
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/creditCardAntiPatternButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_red_dark"
+            app:infoText="@string/anti_pattern_credit_card_info"
+            app:itemLogo="@drawable/ic_disabled_black_24dp"
+            app:labelText="@string/navigation_button_anti_pattern_credit_card_label"
+            app:destinationActivityName="com.example.android.autofill.app.edgecases.CreditCardAntiPatternActivity"/>
+
+        <!--TODO finish multistep impl and show this item-->
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/multistepSignInButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_green_light"
+            app:infoText="@string/multi_step_signin_info"
+            app:itemLogo="@drawable/ic_person_black_24dp"
+            app:labelText="@string/navigation_button_multistep_signin_label"
+            app:destinationActivityName="com.example.android.autofill.app.edgecases.MultipleStepsSignInActivity"/>
+
+        <!--TODO finish multistep impl and show this item-->
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/multistepCreditCardButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_purple"
+            app:infoText="@string/multi_step_cc_info"
+            app:itemLogo="@drawable/ic_spinners_logo_24dp"
+            app:labelText="@string/navigation_button_multistep_cc_label"
+            app:destinationActivityName="com.example.android.autofill.app.edgecases.MultipleStepsCreditCardActivity"/>
+
+        <com.example.android.autofill.app.view.widget.NavigationItem
+            android:id="@+id/badViewStructureAntiPatternButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:imageColor="@android:color/holo_red_dark"
+            app:infoText="@string/anti_pattern_bad_view_structure_info"
+            app:itemLogo="@drawable/ic_disabled_black_24dp"
+            app:labelText="@string/navigation_button_anti_pattern_bad_view_structure_label"
+            app:destinationActivityName="com.example.android.autofill.app.antipatterns.BadViewStructureCreationSignInActivity"/>
+
+    </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/multiple_steps_activity.xml b/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/multiple_steps_activity.xml
new file mode 100644
index 0000000..fe7fd09
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/multiple_steps_activity.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/status"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <LinearLayout
+        android:id="@+id/container"
+        android:layout_width="match_parent"
+        android:layout_height="200dp"
+        android:orientation="horizontal"></LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/prev"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/previous_label" />
+
+        <Button
+            android:id="@+id/next"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/next_label" />
+
+        <Button
+            android:id="@+id/finish"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/finish_label" />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/recycler_view_activity.xml b/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/recycler_view_activity.xml
new file mode 100644
index 0000000..50090ef
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/recycler_view_activity.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:weightSum="1">
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/recyclerView"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="0.8"
+        tools:listItem="@layout/user_data_field" />
+
+    <android.support.constraint.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="0.2">
+
+        <TextView
+            android:id="@+id/clearButton"
+            style="@style/Widget.AppCompat.Button.Borderless"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/spacing_normal"
+            android:layout_marginStart="@dimen/spacing_normal"
+            android:layout_marginTop="@dimen/spacing_normal"
+            android:text="@string/clear_label"
+            android:textColor="@android:color/holo_blue_dark"
+            app:layout_constraintEnd_toStartOf="@+id/submitButton"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="parent" />
+
+        <TextView
+            android:id="@+id/submitButton"
+            style="@style/Widget.AppCompat.Button.Borderless"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="@dimen/spacing_normal"
+            android:layout_marginStart="@dimen/spacing_normal"
+            android:text="@string/submit_label"
+            android:textColor="@android:color/holo_blue_dark"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintStart_toEndOf="@+id/clearButton"
+            app:layout_constraintTop_toTopOf="@+id/clearButton" />
+    </android.support.constraint.ConstraintLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/user_data_field.xml b/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/user_data_field.xml
new file mode 100644
index 0000000..26f0b2e
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/user_data_field.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+-->
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="@dimen/activity_vertical_margin"
+    android:layout_marginBottom="@dimen/activity_vertical_margin"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/spacing_normal"
+        android:layout_marginStart="@dimen/spacing_normal"
+        android:layout_marginEnd="@dimen/spacing_normal"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/field"
+        app:layout_constraintBottom_toBottomOf="@+id/field"
+        tools:background="@drawable/ic_person_black_24dp" />
+
+    <TextView
+        android:id="@+id/label"
+        style="@style/TextAppearance.AppCompat.Body1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/spacing_normal"
+        android:layout_marginEnd="@dimen/spacing_normal"
+        app:layout_constraintBottom_toBottomOf="@+id/field"
+        app:layout_constraintTop_toTopOf="@+id/field"
+        app:layout_constraintStart_toEndOf="@+id/icon"
+        tools:text="@string/recycler_view_label_name" />
+
+    <EditText
+        android:id="@+id/field"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/spacing_normal"
+        android:layout_marginEnd="@dimen/spacing_normal"
+        android:ems="10"
+        app:layout_constraintStart_toEndOf="@+id/label"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="Doug" />
+</android.support.constraint.ConstraintLayout>
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/virtual_compat_mode_login_activity.xml b/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/virtual_compat_mode_login_activity.xml
new file mode 100644
index 0000000..aeeac4c
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/res/layout/virtual_compat_mode_login_activity.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+-->
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin">
+
+    <TextView
+        android:id="@+id/custom_virtual_compat_mode_login_header"
+        style="@style/TextAppearance.AppCompat.Large"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:gravity="center"
+        android:text="@string/navigation_button_custom_virtual_view_compat_mode_login_label"
+        app:layout_constraintEnd_toStartOf="@+id/imageButton"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintHorizontal_chainStyle="spread"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <com.example.android.autofill.app.view.widget.InfoButton
+        android:id="@+id/imageButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="@drawable/ic_info_black_24dp"
+        app:dialogText="@string/custom_virtual_login_info"
+        app:layout_constraintBottom_toBottomOf="@+id/custom_virtual_compat_mode_login_header"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintStart_toEndOf="@+id/custom_virtual_login_header"
+        app:layout_constraintTop_toTopOf="@+id/custom_virtual_login_header" />
+
+    <com.example.android.autofill.app.view.autofillable.CustomVirtualViewCompatMode
+        android:id="@+id/custom_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/custom_view_height"
+        android:layout_marginStart="8dp"
+        android:layout_marginEnd="8dp"
+        android:paddingEnd="@dimen/spacing_large"
+        android:paddingStart="@dimen/spacing_large"
+        android:paddingTop="@dimen/spacing_large"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/custom_virtual_compat_mode_login_header" />
+
+    <TextView
+        android:id="@+id/clear"
+        style="@style/Widget.AppCompat.Button.Borderless"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/spacing_normal"
+        android:layout_marginTop="@dimen/spacing_normal"
+        android:text="@string/clear_label"
+        android:textColor="@android:color/holo_blue_dark"
+        app:layout_constraintEnd_toStartOf="@+id/login"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/custom_view" />
+
+    <TextView
+        android:id="@+id/login"
+        style="@style/Widget.AppCompat.Button.Borderless"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/spacing_normal"
+        android:layout_marginStart="@dimen/spacing_normal"
+        android:text="@string/login_label"
+        android:textColor="@android:color/holo_blue_dark"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintStart_toEndOf="@+id/clear"
+        app:layout_constraintTop_toTopOf="@+id/clear" />
+</android.support.constraint.ConstraintLayout>
diff --git a/prebuilts/gradle/AutofillFramework/Application/src/main/res/values/styles.xml b/prebuilts/gradle/AutofillFramework/Application/src/main/res/values/styles.xml
index e7549ac..653fc84 100644
--- a/prebuilts/gradle/AutofillFramework/Application/src/main/res/values/styles.xml
+++ b/prebuilts/gradle/AutofillFramework/Application/src/main/res/values/styles.xml
@@ -30,4 +30,4 @@
     <style name="MyDatePickerStyle" parent="@android:style/Widget.Material.DatePicker">
         <item name="android:datePickerMode">spinner</item>
     </style>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/AutofillDaoTest.java b/prebuilts/gradle/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/AutofillDaoTest.java
new file mode 100644
index 0000000..c76ba43
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/AutofillDaoTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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 com.example.android.autofill.service.data.source.local;
+
+import android.arch.persistence.room.Room;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+
+import com.example.android.autofill.service.data.source.local.db.AutofillDatabase;
+import com.example.android.autofill.service.model.AutofillDataset;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.google.common.collect.ImmutableList;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.UUID;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.hasSize;
+
+@RunWith(AndroidJUnit4.class)
+public class AutofillDaoTest {
+    private final AutofillDataset mDataset =
+            new AutofillDataset(UUID.randomUUID().toString(),
+                    "dataset-1", InstrumentationRegistry.getContext().getPackageName());
+    private final FilledAutofillField mUsernameField =
+            new FilledAutofillField(mDataset.getId(), View.AUTOFILL_HINT_USERNAME, "login");
+    private final FilledAutofillField mPasswordField =
+            new FilledAutofillField(mDataset.getId(), View.AUTOFILL_HINT_PASSWORD, "password");
+
+    private AutofillDatabase mDatabase;
+
+    @Before
+    public void setup() {
+        // using an in-memory database because the information stored here disappears when the
+        // process is killed
+        mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(),
+                AutofillDatabase.class).build();
+
+    }
+
+    @After
+    public void closeDb() {
+        mDatabase.close();
+    }
+
+    @Test
+    public void insertFilledAutofillFieldAndGet() {
+        DatasetWithFilledAutofillFields datasetWithFilledAutofillFields =
+                new DatasetWithFilledAutofillFields();
+        datasetWithFilledAutofillFields.autofillDataset = mDataset;
+        datasetWithFilledAutofillFields.filledAutofillFields =
+                Arrays.asList(mUsernameField, mPasswordField);
+        datasetWithFilledAutofillFields.filledAutofillFields
+                .sort(Comparator.comparing(FilledAutofillField::getFieldTypeName));
+
+        // When inserting a page's autofill fields.
+        mDatabase.autofillDao().insertAutofillDataset(mDataset);
+        mDatabase.autofillDao().insertFilledAutofillFields(
+                datasetWithFilledAutofillFields.filledAutofillFields);
+
+        // Represents all hints of all fields on page.
+        List<String> allHints = ImmutableList.of(View.AUTOFILL_HINT_USERNAME,
+                View.AUTOFILL_HINT_PASSWORD);
+        List<DatasetWithFilledAutofillFields> loadedDatasets = mDatabase.autofillDao()
+                .getDatasets(allHints);
+        loadedDatasets.get(0).filledAutofillFields.sort(
+                Comparator.comparing(FilledAutofillField::getFieldTypeName));
+        assertThat(loadedDatasets, contains(datasetWithFilledAutofillFields));
+        assertThat(loadedDatasets, hasSize(1));
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/LocalDataSourceTest.java b/prebuilts/gradle/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/LocalDataSourceTest.java
new file mode 100644
index 0000000..2f445c5
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/LocalDataSourceTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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 com.example.android.autofill.service.data.source.local;
+
+import android.arch.persistence.room.Room;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
+import com.example.android.autofill.service.data.source.local.db.AutofillDatabase;
+import com.example.android.autofill.service.util.SingleExecutors;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class LocalDataSourceTest {
+
+    private LocalAutofillDataSource mLocalDataSource;
+    private AutofillDatabase mDatabase;
+
+    @Before
+    public void setup() {
+        // using an in-memory database for testing, since it doesn't survive killing the process
+        mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(),
+                AutofillDatabase.class)
+                .build();
+        AutofillDao tasksDao = mDatabase.autofillDao();
+        SharedPreferences sharedPreferences = InstrumentationRegistry.getContext()
+                .getSharedPreferences(LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE);
+        // Make sure that we're not keeping a reference to the wrong instance.
+        LocalAutofillDataSource.clearInstance();
+        mLocalDataSource = LocalAutofillDataSource.getInstance(sharedPreferences,
+                tasksDao, new SingleExecutors());
+    }
+
+    @After
+    public void cleanUp() {
+        try {
+            mDatabase.close();
+        } finally {
+            LocalAutofillDataSource.clearInstance();
+        }
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/util/SingleExecutors.java b/prebuilts/gradle/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/util/SingleExecutors.java
new file mode 100644
index 0000000..baac2b1
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/util/SingleExecutors.java
@@ -0,0 +1,30 @@
+/*
+ * 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 com.example.android.autofill.service.util;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Allow instant execution of tasks.
+ */
+public final class SingleExecutors extends AppExecutors {
+    private static Executor sInstance = Runnable::run;
+
+    public SingleExecutors() {
+        super(sInstance, sInstance, sInstance);
+    }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/AndroidManifest.xml b/prebuilts/gradle/AutofillFramework/afservice/src/main/AndroidManifest.xml
index 9bfff73..f3ffe2d 100644
--- a/prebuilts/gradle/AutofillFramework/afservice/src/main/AndroidManifest.xml
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/AndroidManifest.xml
@@ -51,6 +51,12 @@
             android:name=".simple.MultiStepsService"
             android:label="Multiple-steps Service"
             android:permission="android.permission.BIND_AUTOFILL_SERVICE">
+            android:name=".simple.HeuristicsService"
+            android:label="Heuristics Autofill Service"
+            android:permission="android.permission.BIND_AUTOFILL_SERVICE">
+            <meta-data
+                android:name="android.autofill"
+                android:resource="@xml/heuristics_service"/>
             <intent-filter>
                 <action android:name="android.service.autofill.AutofillService" />
             </intent-filter>
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ClientParser.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ClientParser.java
new file mode 100644
index 0000000..048c001
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ClientParser.java
@@ -0,0 +1,72 @@
+/*
+ * 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 com.example.android.autofill.service;
+
+import android.app.assist.AssistStructure;
+import android.support.annotation.NonNull;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+import static android.app.assist.AssistStructure.ViewNode;
+
+/**
+ * Wrapper for {@link AssistStructure} to make it easy to parse.
+ */
+public final class ClientParser {
+    private final List<AssistStructure> mStructures;
+
+    public ClientParser(@NonNull List<AssistStructure> structures) {
+        Preconditions.checkNotNull(structures);
+        mStructures = structures;
+    }
+
+    public ClientParser(@NonNull AssistStructure structure) {
+        this(ImmutableList.of(structure));
+    }
+
+    /**
+     * Traverses through the {@link AssistStructure} and does something at each {@link ViewNode}.
+     *
+     * @param processor contains action to be performed on each {@link ViewNode}.
+     */
+    public void parse(NodeProcessor processor) {
+        for (AssistStructure structure : mStructures) {
+            int nodes = structure.getWindowNodeCount();
+            for (int i = 0; i < nodes; i++) {
+                AssistStructure.ViewNode viewNode = structure.getWindowNodeAt(i).getRootViewNode();
+                traverseRoot(viewNode, processor);
+            }
+        }
+    }
+
+    private void traverseRoot(AssistStructure.ViewNode viewNode, NodeProcessor processor) {
+        processor.processNode(viewNode);
+        int childrenSize = viewNode.getChildCount();
+        if (childrenSize > 0) {
+            for (int i = 0; i < childrenSize; i++) {
+                traverseRoot(viewNode.getChildAt(i), processor);
+            }
+        }
+    }
+
+    public interface NodeProcessor {
+        void processNode(ViewNode node);
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ManualActivity.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ManualActivity.java
new file mode 100644
index 0000000..a9acaa5
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ManualActivity.java
@@ -0,0 +1,345 @@
+/*
+ * 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 com.example.android.autofill.service;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.assist.AssistStructure;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.DividerItemDecoration;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.example.android.autofill.service.data.ClientViewMetadata;
+import com.example.android.autofill.service.data.ClientViewMetadataBuilder;
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.data.adapter.DatasetAdapter;
+import com.example.android.autofill.service.data.adapter.ResponseAdapter;
+import com.example.android.autofill.service.data.source.DefaultFieldTypesSource;
+import com.example.android.autofill.service.data.source.local.DefaultFieldTypesLocalJsonSource;
+import com.example.android.autofill.service.data.source.local.LocalAutofillDataSource;
+import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
+import com.example.android.autofill.service.data.source.local.db.AutofillDatabase;
+import com.example.android.autofill.service.model.AutofillDataset;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.example.android.autofill.service.settings.MyPreferences;
+import com.example.android.autofill.service.util.AppExecutors;
+import com.google.common.collect.ImmutableList;
+import com.google.gson.GsonBuilder;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE;
+import static android.view.autofill.AutofillManager.EXTRA_AUTHENTICATION_RESULT;
+import static com.example.android.autofill.service.util.Util.logd;
+
+/**
+ * When the user long-presses on an autofillable field and selects "Autofill", this activity is
+ * launched to allow the user to select the dataset.
+ */
+public class ManualActivity extends AppCompatActivity {
+
+    private static final int RC_SELECT_FIELD = 1;
+
+    // Unique id for dataset intents.
+    private static int sDatasetPendingIntentId = 0;
+
+    private LocalAutofillDataSource mLocalAutofillDataSource;
+    private DatasetAdapter mDatasetAdapter;
+    private ResponseAdapter mResponseAdapter;
+    private ClientViewMetadata mClientViewMetadata;
+    private String mPackageName;
+    private Intent mReplyIntent;
+    private MyPreferences mPreferences;
+    private List<DatasetWithFilledAutofillFields> mAllDatasets;
+    private RecyclerView mRecyclerView;
+
+    public static IntentSender getManualIntentSenderForResponse(Context context) {
+        final Intent intent = new Intent(context, ManualActivity.class);
+        return PendingIntent.getActivity(context, 0, intent,
+                PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.multidataset_service_manual_activity);
+        SharedPreferences sharedPreferences =
+                getSharedPreferences(LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE);
+        DefaultFieldTypesSource defaultFieldTypesSource =
+                DefaultFieldTypesLocalJsonSource.getInstance(getResources(),
+                        new GsonBuilder().create());
+        AutofillDao autofillDao = AutofillDatabase.getInstance(this,
+                defaultFieldTypesSource, new AppExecutors()).autofillDao();
+        mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(sharedPreferences,
+                autofillDao, new AppExecutors());
+        mPackageName = getPackageName();
+        mPreferences = MyPreferences.getInstance(this);
+        mRecyclerView = findViewById(R.id.suggestionsList);
+        mRecyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL));
+        mLocalAutofillDataSource.getAllAutofillDatasets(
+                new DataCallback<List<DatasetWithFilledAutofillFields>>() {
+                    @Override
+                    public void onLoaded(List<DatasetWithFilledAutofillFields> datasets) {
+                        mAllDatasets = datasets;
+                        buildAdapter();
+                    }
+
+                    @Override
+                    public void onDataNotAvailable(String msg, Object... params) {
+
+                    }
+                });
+    }
+
+    private void buildAdapter() {
+        List<String> datasetIds = new ArrayList<>();
+        List<String> datasetNames = new ArrayList<>();
+        List<List<String>> allFieldTypes = new ArrayList<>();
+        for (DatasetWithFilledAutofillFields dataset : mAllDatasets) {
+            String datasetName = dataset.autofillDataset.getDatasetName();
+            String datasetId = dataset.autofillDataset.getId();
+            List<String> fieldTypes = new ArrayList<>();
+            for (FilledAutofillField filledAutofillField : dataset.filledAutofillFields) {
+                fieldTypes.add(filledAutofillField.getFieldTypeName());
+            }
+            datasetIds.add(datasetId);
+            datasetNames.add(datasetName);
+            allFieldTypes.add(fieldTypes);
+        }
+        AutofillDatasetsAdapter adapter = new AutofillDatasetsAdapter(datasetIds, datasetNames,
+                allFieldTypes, this);
+        mRecyclerView.setAdapter(adapter);
+    }
+
+    @Override
+    public void finish() {
+        if (mReplyIntent != null) {
+            setResult(RESULT_OK, mReplyIntent);
+        } else {
+            setResult(RESULT_CANCELED);
+        }
+        super.finish();
+    }
+
+    private void onFieldSelected(FilledAutofillField field, FieldType fieldType) {
+        DatasetWithFilledAutofillFields datasetWithFilledAutofillFields = new DatasetWithFilledAutofillFields();
+        String newDatasetId = UUID.randomUUID().toString();
+        FilledAutofillField copyOfField = new FilledAutofillField(newDatasetId,
+                field.getFieldTypeName(), field.getTextValue(), field.getDateValue(),
+                field.getToggleValue());
+        String datasetName = "dataset-manual";
+        AutofillDataset autofillDataset = new AutofillDataset(newDatasetId, datasetName, mPackageName);
+        datasetWithFilledAutofillFields.filledAutofillFields = ImmutableList.of(copyOfField);
+        datasetWithFilledAutofillFields.autofillDataset = autofillDataset;
+        Intent intent = getIntent();
+        AssistStructure structure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE);
+        ClientParser clientParser = new ClientParser(structure);
+        mReplyIntent = new Intent();
+        mLocalAutofillDataSource.getFieldTypeByAutofillHints(
+                new DataCallback<HashMap<String, FieldTypeWithHeuristics>>() {
+                    @Override
+                    public void onLoaded(HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint) {
+                        ClientViewMetadataBuilder builder = new ClientViewMetadataBuilder(clientParser,
+                                fieldTypesByAutofillHint);
+                        mClientViewMetadata = builder.buildClientViewMetadata();
+                        mDatasetAdapter = new DatasetAdapter(clientParser);
+                        mResponseAdapter = new ResponseAdapter(ManualActivity.this,
+                                mClientViewMetadata, mPackageName, mDatasetAdapter);
+                        FillResponse fillResponse = mResponseAdapter.buildResponseForFocusedNode(
+                                datasetName, field, fieldType);
+                        setResponseIntent(fillResponse);
+                        finish();
+                    }
+
+                    @Override
+                    public void onDataNotAvailable(String msg, Object... params) {
+                    }
+                });
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode != RC_SELECT_FIELD || resultCode != RESULT_OK) {
+            logd("Ignoring requestCode == %d | resultCode == %d", requestCode,
+                    resultCode);
+            return;
+        }
+        String datasetId = data.getStringExtra(ManualFieldPickerActivity.EXTRA_SELECTED_FIELD_DATASET_ID);
+        String fieldTypeName = data.getStringExtra(ManualFieldPickerActivity.EXTRA_SELECTED_FIELD_TYPE_NAME);
+        mLocalAutofillDataSource.getFilledAutofillField(datasetId, fieldTypeName, new DataCallback<FilledAutofillField>() {
+            @Override
+            public void onLoaded(FilledAutofillField field) {
+                mLocalAutofillDataSource.getFieldType(field.getFieldTypeName(), new DataCallback<FieldType>() {
+                    @Override
+                    public void onLoaded(FieldType fieldType) {
+                        onFieldSelected(field, fieldType);
+                    }
+
+                    @Override
+                    public void onDataNotAvailable(String msg, Object... params) {
+
+                    }
+                });
+            }
+
+            @Override
+            public void onDataNotAvailable(String msg, Object... params) {
+
+            }
+        });
+    }
+
+
+    private void updateHeuristics() {
+//        TODO: update heuristics in data source; something like:
+//        mLocalAutofillDataSource.getAutofillDataset(mClientViewMetadata.getAllHints(),
+//                datasetName, new DataCallback<DatasetWithFilledAutofillFields>() {
+//                    @Override
+//                    public void onLoaded(DatasetWithFilledAutofillFields dataset) {
+//                        String datasetName = dataset.autofillDataset.getDatasetName();
+//                        RemoteViews remoteViews = RemoteViewsHelper.viewsWithNoAuth(
+//                                mPackageName, datasetName);
+//                        setDatasetIntent(mDatasetAdapter.buildDataset(fieldTypesByAutofillHint,
+//                                dataset, remoteViews));
+//                        finish();
+//                    }
+//
+//                    @Override
+//                    public void onDataNotAvailable(String msg, Object... params) {
+//                        logw(msg, params);
+//                        finish();
+//                    }
+//                });
+    }
+
+    private void setResponseIntent(FillResponse fillResponse) {
+        mReplyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, fillResponse);
+    }
+
+    private void setDatasetIntent(Dataset dataset) {
+        mReplyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, dataset);
+    }
+
+    /**
+     * Adapter for the {@link RecyclerView} that holds a list of datasets.
+     */
+    private static class AutofillDatasetsAdapter extends RecyclerView.Adapter<DatasetViewHolder> {
+
+        private final List<String> mDatasetIds;
+        private final List<String> mDatasetNames;
+        private final List<List<String>> mFieldTypes;
+        private final Activity mActivity;
+
+        AutofillDatasetsAdapter(List<String> datasetIds, List<String> datasetNames,
+                List<List<String>> fieldTypes, Activity activity) {
+            mDatasetIds = datasetIds;
+            mDatasetNames = datasetNames;
+            mFieldTypes = fieldTypes;
+            mActivity = activity;
+        }
+
+        @Override
+        public DatasetViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            return DatasetViewHolder.newInstance(parent, mActivity);
+        }
+
+        @Override
+        public void onBindViewHolder(final DatasetViewHolder holder, final int position) {
+            holder.bind(mDatasetIds.get(position), mDatasetNames.get(position),
+                    mFieldTypes.get(position));
+        }
+
+        @Override
+        public int getItemCount() {
+            return mDatasetNames.size();
+        }
+    }
+
+    /**
+     * Contains views needed in each row of the list of datasets.
+     */
+    private static class DatasetViewHolder extends RecyclerView.ViewHolder {
+        private final View mRootView;
+        private final TextView mDatasetNameText;
+        private final TextView mFieldTypesText;
+        private final Activity mActivity;
+
+        public DatasetViewHolder(View itemView, Activity activity) {
+            super(itemView);
+            mRootView = itemView;
+            mDatasetNameText = itemView.findViewById(R.id.datasetName);
+            mFieldTypesText = itemView.findViewById(R.id.fieldTypes);
+            mActivity = activity;
+        }
+
+        public static DatasetViewHolder newInstance(ViewGroup parent, Activity activity) {
+            return new DatasetViewHolder(LayoutInflater.from(parent.getContext())
+                    .inflate(R.layout.dataset_suggestion, parent, false), activity);
+        }
+
+        public void bind(String datasetId, String datasetName, List<String> fieldTypes) {
+            mDatasetNameText.setText(datasetName);
+            String firstFieldType = null;
+            String secondFieldType = null;
+            int numOfFieldTypes = 0;
+            if (fieldTypes != null) {
+                numOfFieldTypes = fieldTypes.size();
+                if (numOfFieldTypes > 0) {
+                    firstFieldType = fieldTypes.get(0);
+                }
+                if (numOfFieldTypes > 1) {
+                    secondFieldType = fieldTypes.get(1);
+                }
+            }
+            String fieldTypesString;
+            if (numOfFieldTypes == 1) {
+                fieldTypesString = "Contains data for " + firstFieldType + ".";
+            } else if (numOfFieldTypes == 2) {
+                fieldTypesString = "Contains data for " + firstFieldType + " and " + secondFieldType + ".";
+            } else if (numOfFieldTypes > 2) {
+                fieldTypesString = "Contains data for " + firstFieldType + ", " + secondFieldType + ", and more.";
+            } else {
+                fieldTypesString = "Ignore: Contains no data.";
+            }
+            mFieldTypesText.setText(fieldTypesString);
+            mRootView.setOnClickListener((view) -> {
+                Intent intent = ManualFieldPickerActivity.getIntent(mActivity, datasetId);
+                mActivity.startActivityForResult(intent, RC_SELECT_FIELD);
+            });
+        }
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ManualFieldPickerActivity.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ManualFieldPickerActivity.java
new file mode 100644
index 0000000..608ac95
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ManualFieldPickerActivity.java
@@ -0,0 +1,160 @@
+/*
+ * 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 com.example.android.autofill.service;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.DividerItemDecoration;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.data.source.DefaultFieldTypesSource;
+import com.example.android.autofill.service.data.source.local.DefaultFieldTypesLocalJsonSource;
+import com.example.android.autofill.service.data.source.local.LocalAutofillDataSource;
+import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
+import com.example.android.autofill.service.data.source.local.db.AutofillDatabase;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.example.android.autofill.service.util.AppExecutors;
+import com.google.gson.GsonBuilder;
+
+import java.util.List;
+
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+
+public class ManualFieldPickerActivity extends AppCompatActivity {
+    private static final String EXTRA_DATASET_ID = "extra_dataset_id";
+    public static final String EXTRA_SELECTED_FIELD_DATASET_ID = "selected_field_dataset_id";
+    public static final String EXTRA_SELECTED_FIELD_TYPE_NAME = "selected_field_type_name";
+
+    private LocalAutofillDataSource mLocalAutofillDataSource;
+
+    private RecyclerView mRecyclerView;
+    private TextView mListTitle;
+    private DatasetWithFilledAutofillFields mDataset;
+
+    public static Intent getIntent(Context originContext, String datasetId) {
+        Intent intent = new Intent(originContext, ManualFieldPickerActivity.class);
+        intent.putExtra(EXTRA_DATASET_ID, datasetId);
+        return intent;
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_field_picker);
+        SharedPreferences sharedPreferences = getSharedPreferences(
+                LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE);
+        DefaultFieldTypesSource defaultFieldTypesSource =
+                DefaultFieldTypesLocalJsonSource.getInstance(getResources(),
+                        new GsonBuilder().create());
+        AutofillDao autofillDao = AutofillDatabase.getInstance(this,
+                defaultFieldTypesSource, new AppExecutors()).autofillDao();
+        String datasetId = getIntent().getStringExtra(EXTRA_DATASET_ID);
+        mRecyclerView = findViewById(R.id.fieldsList);
+        mRecyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL));
+        mListTitle = findViewById(R.id.listTitle);
+        mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(sharedPreferences,
+                autofillDao, new AppExecutors());
+        mLocalAutofillDataSource.getAutofillDatasetWithId(datasetId,
+                new DataCallback<DatasetWithFilledAutofillFields>() {
+                    @Override
+                    public void onLoaded(DatasetWithFilledAutofillFields dataset) {
+                        mDataset = dataset;
+                        if (mDataset != null) {
+                            onLoadedDataset();
+                        }
+                    }
+
+                    @Override
+                    public void onDataNotAvailable(String msg, Object... params) {
+
+                    }
+                });
+    }
+
+    public void onSelectedDataset(FilledAutofillField field) {
+        Intent data = new Intent()
+                .putExtra(EXTRA_SELECTED_FIELD_DATASET_ID, field.getDatasetId())
+                .putExtra(EXTRA_SELECTED_FIELD_TYPE_NAME, field.getFieldTypeName());
+        setResult(RESULT_OK, data);
+        finish();
+    }
+
+    public void onLoadedDataset() {
+        FieldsAdapter fieldsAdapter = new FieldsAdapter(this, mDataset.filledAutofillFields);
+        mRecyclerView.setAdapter(fieldsAdapter);
+        mListTitle.setText(getString(R.string.manual_data_picker_title,
+                mDataset.autofillDataset.getDatasetName()));
+    }
+
+    private static class FieldsAdapter extends RecyclerView.Adapter<FieldViewHolder> {
+        private final Activity mActivity;
+        private final List<FilledAutofillField> mFields;
+
+        public FieldsAdapter(Activity activity, List<FilledAutofillField> fields) {
+            mActivity = activity;
+            mFields = fields;
+        }
+
+        @Override
+        public FieldViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            return new FieldViewHolder(LayoutInflater.from(parent.getContext())
+                    .inflate(R.layout.dataset_field, parent, false), mActivity);
+        }
+
+        @Override
+        public void onBindViewHolder(FieldViewHolder holder, int position) {
+            FilledAutofillField field = mFields.get(position);
+            holder.bind(field);
+        }
+
+        @Override
+        public int getItemCount() {
+            return mFields.size();
+        }
+    }
+
+    private static class FieldViewHolder extends RecyclerView.ViewHolder {
+        private final View mRootView;
+        private final TextView mFieldTypeText;
+        private final Activity mActivity;
+
+        public FieldViewHolder(View itemView, Activity activity) {
+            super(itemView);
+            mRootView = itemView;
+            mFieldTypeText = itemView.findViewById(R.id.fieldType);
+            mActivity = activity;
+        }
+
+        public void bind(FilledAutofillField field) {
+            mFieldTypeText.setText(field.getFieldTypeName());
+            mRootView.setOnClickListener((view) -> {
+                ((ManualFieldPickerActivity) mActivity).onSelectedDataset(field);
+            });
+        }
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/RemoteViewsHelper.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/RemoteViewsHelper.java
new file mode 100644
index 0000000..b925b95
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/RemoteViewsHelper.java
@@ -0,0 +1,44 @@
+/*
+ * 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 com.example.android.autofill.service;
+
+import android.support.annotation.DrawableRes;
+import android.widget.RemoteViews;
+
+/**
+ * This is a class containing helper methods for building Autofill Datasets and Responses.
+ */
+public final class RemoteViewsHelper {
+    private RemoteViewsHelper() {
+    }
+
+    public static RemoteViews viewsWithAuth(String packageName, String text) {
+        return simpleRemoteViews(packageName, text, R.drawable.ic_lock_black_24dp);
+    }
+
+    public static RemoteViews viewsWithNoAuth(String packageName, String text) {
+        return simpleRemoteViews(packageName, text, R.drawable.ic_person_black_24dp);
+    }
+
+    private static RemoteViews simpleRemoteViews(String packageName, String remoteViewsText,
+            @DrawableRes int drawableId) {
+        RemoteViews presentation = new RemoteViews(packageName,
+                R.layout.multidataset_service_list_item);
+        presentation.setTextViewText(R.id.text, remoteViewsText);
+        presentation.setImageViewResource(R.id.icon, drawableId);
+        return presentation;
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/AutofillDataBuilder.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/AutofillDataBuilder.java
new file mode 100644
index 0000000..0110bdd
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/AutofillDataBuilder.java
@@ -0,0 +1,25 @@
+/*
+ * 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 com.example.android.autofill.service.data;
+
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+
+import java.util.List;
+
+public interface AutofillDataBuilder {
+    List<DatasetWithFilledAutofillFields> buildDatasetsByPartition(int datasetNumber);
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientAutofillDataBuilder.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientAutofillDataBuilder.java
new file mode 100644
index 0000000..2d26833
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientAutofillDataBuilder.java
@@ -0,0 +1,160 @@
+/*
+ * 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 com.example.android.autofill.service.data;
+
+import android.app.assist.AssistStructure;
+import android.support.annotation.NonNull;
+import android.view.View;
+import android.view.autofill.AutofillValue;
+
+import com.example.android.autofill.service.AutofillHints;
+import com.example.android.autofill.service.ClientParser;
+import com.example.android.autofill.service.model.AutofillDataset;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.google.common.collect.ImmutableList;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import static com.example.android.autofill.service.util.Util.loge;
+
+public class ClientAutofillDataBuilder implements AutofillDataBuilder {
+    private final ClientParser mClientParser;
+    private final HashMap<String, FieldTypeWithHeuristics> mFieldTypesByAutofillHint;
+    private final String mPackageName;
+
+    public ClientAutofillDataBuilder(HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint,
+            String packageName, ClientParser clientParser) {
+        mClientParser = clientParser;
+        mFieldTypesByAutofillHint = fieldTypesByAutofillHint;
+        mPackageName = packageName;
+    }
+
+    @Override
+    public List<DatasetWithFilledAutofillFields> buildDatasetsByPartition(int datasetNumber) {
+        ImmutableList.Builder<DatasetWithFilledAutofillFields> listBuilder =
+                new ImmutableList.Builder<>();
+        for (int partition : AutofillHints.PARTITIONS) {
+            AutofillDataset autofillDataset = new AutofillDataset(UUID.randomUUID().toString(),
+                    "dataset-" + datasetNumber + "." + partition, mPackageName);
+            DatasetWithFilledAutofillFields dataset =
+                    buildDatasetForPartition(autofillDataset, partition);
+            if (dataset != null && dataset.filledAutofillFields != null) {
+                listBuilder.add(dataset);
+            }
+        }
+        return listBuilder.build();
+    }
+
+    /**
+     * Parses a client view structure and build a dataset (in the form of a
+     * {@link DatasetWithFilledAutofillFields}) from the view metadata found.
+     */
+    private DatasetWithFilledAutofillFields buildDatasetForPartition(AutofillDataset dataset,
+            int partition) {
+        DatasetWithFilledAutofillFields datasetWithFilledAutofillFields =
+                new DatasetWithFilledAutofillFields();
+        datasetWithFilledAutofillFields.autofillDataset = dataset;
+        mClientParser.parse((node) ->
+                parseAutofillFields(node, datasetWithFilledAutofillFields, partition)
+        );
+        return datasetWithFilledAutofillFields;
+
+    }
+
+    private void parseAutofillFields(AssistStructure.ViewNode viewNode,
+            DatasetWithFilledAutofillFields datasetWithFilledAutofillFields, int partition) {
+        String[] hints = viewNode.getAutofillHints();
+        if (hints == null || hints.length == 0) {
+            return;
+        }
+        AutofillValue autofillValue = viewNode.getAutofillValue();
+        String textValue = null;
+        Long dateValue = null;
+        Boolean toggleValue = null;
+        CharSequence[] autofillOptions = null;
+        Integer listIndex = null;
+        if (autofillValue != null) {
+            if (autofillValue.isText()) {
+                // Using toString of AutofillValue.getTextValue in order to save it to
+                // SharedPreferences.
+                textValue = autofillValue.getTextValue().toString();
+            } else if (autofillValue.isDate()) {
+                dateValue = autofillValue.getDateValue();
+            } else if (autofillValue.isList()) {
+                autofillOptions = viewNode.getAutofillOptions();
+                listIndex = autofillValue.getListValue();
+            } else if (autofillValue.isToggle()) {
+                toggleValue = autofillValue.getToggleValue();
+            }
+        }
+        appendViewMetadata(datasetWithFilledAutofillFields,
+                hints, partition, textValue, dateValue, toggleValue,
+                autofillOptions, listIndex);
+    }
+
+    private void appendViewMetadata(@NonNull DatasetWithFilledAutofillFields
+            datasetWithFilledAutofillFields, @NonNull String[] hints, int partition,
+            @Nullable String textValue, @Nullable Long dateValue, @Nullable Boolean toggleValue,
+            @Nullable CharSequence[] autofillOptions, @Nullable Integer listIndex) {
+        for (int i = 0; i < hints.length; i++) {
+            String hint = hints[i];
+            // Then check if the "actual" hint is supported.
+            FieldTypeWithHeuristics fieldTypeWithHeuristics = mFieldTypesByAutofillHint.get(hint);
+            if (fieldTypeWithHeuristics != null) {
+                FieldType fieldType = fieldTypeWithHeuristics.fieldType;
+                if (!AutofillHints.matchesPartition(fieldType.getPartition(), partition)) {
+                    continue;
+                }
+                    // Only add the field if the hint is supported by the type.
+                if (textValue != null) {
+                        if (!fieldType.getAutofillTypes().ints.contains(View.AUTOFILL_TYPE_TEXT)) {
+                            loge("Text is invalid type for hint '%s'", hint);
+                        }
+                    }
+                if (autofillOptions != null && listIndex != null &&
+                        autofillOptions.length > listIndex) {
+                    if (!fieldType.getAutofillTypes().ints.contains(View.AUTOFILL_TYPE_LIST)) {
+                        loge("List is invalid type for hint '%s'", hint);
+                    }
+                    textValue = autofillOptions[listIndex].toString();
+                }
+                if (dateValue != null) {
+                    if (!fieldType.getAutofillTypes().ints.contains(View.AUTOFILL_TYPE_DATE)) {
+                        loge("Date is invalid type for hint '%s'", hint);
+                    }
+                }
+                if (toggleValue != null) {
+                    if (!fieldType.getAutofillTypes().ints.contains(View.AUTOFILL_TYPE_TOGGLE)) {
+                        loge("Toggle is invalid type for hint '%s'", hint);
+                    }
+                }
+                String datasetId = datasetWithFilledAutofillFields.autofillDataset.getId();
+                datasetWithFilledAutofillFields.add(new FilledAutofillField(datasetId,
+                        fieldType.getTypeName(), textValue, dateValue, toggleValue));
+            } else {
+                loge("Invalid hint: %s", hint);
+            }
+        }
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadata.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadata.java
new file mode 100644
index 0000000..85b789e
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadata.java
@@ -0,0 +1,75 @@
+/*
+ * 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 com.example.android.autofill.service.data;
+
+import android.service.autofill.SaveInfo;
+import android.view.autofill.AutofillId;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * In this simple implementation, the only view data we collect from the client are autofill hints
+ * of the views in the view hierarchy, the corresponding autofill IDs, and the {@link SaveInfo}
+ * based on the hints.
+ */
+public class ClientViewMetadata {
+    private final List<String> mAllHints;
+    private final int mSaveType;
+    private final AutofillId[] mAutofillIds;
+    private final String mWebDomain;
+    private final AutofillId[] mFocusedIds;
+
+    public ClientViewMetadata(List<String> allHints, int saveType, AutofillId[] autofillIds,
+            AutofillId[] focusedIds, String webDomain) {
+        mAllHints = allHints;
+        mSaveType = saveType;
+        mAutofillIds = autofillIds;
+        mWebDomain = webDomain;
+        mFocusedIds = focusedIds;
+    }
+
+    public List<String> getAllHints() {
+        return mAllHints;
+    }
+
+    public AutofillId[] getAutofillIds() {
+        return mAutofillIds;
+    }
+
+    public AutofillId[] getFocusedIds() {
+        return mFocusedIds;
+    }
+
+    public int getSaveType() {
+        return mSaveType;
+    }
+
+    public String getWebDomain() {
+        return mWebDomain;
+    }
+
+    @Override public String toString() {
+        return "ClientViewMetadata{" +
+                "mAllHints=" + mAllHints +
+                ", mSaveType=" + mSaveType +
+                ", mAutofillIds=" + Arrays.toString(mAutofillIds) +
+                ", mWebDomain='" + mWebDomain + '\'' +
+                ", mFocusedIds=" + Arrays.toString(mFocusedIds) +
+                '}';
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadataBuilder.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadataBuilder.java
new file mode 100644
index 0000000..40a67ef
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadataBuilder.java
@@ -0,0 +1,91 @@
+/*
+ * 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 com.example.android.autofill.service.data;
+
+
+import android.app.assist.AssistStructure;
+import android.util.MutableInt;
+import android.view.autofill.AutofillId;
+
+import com.example.android.autofill.service.ClientParser;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import static com.example.android.autofill.service.util.Util.logd;
+
+public class ClientViewMetadataBuilder {
+    private ClientParser mClientParser;
+    private HashMap<String, FieldTypeWithHeuristics> mFieldTypesByAutofillHint;
+
+    public ClientViewMetadataBuilder(ClientParser parser,
+            HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint) {
+        mClientParser = parser;
+        mFieldTypesByAutofillHint = fieldTypesByAutofillHint;
+    }
+
+    public ClientViewMetadata buildClientViewMetadata() {
+        List<String> allHints = new ArrayList<>();
+        MutableInt saveType = new MutableInt(0);
+        List<AutofillId> autofillIds = new ArrayList<>();
+        StringBuilder webDomainBuilder = new StringBuilder();
+        List<AutofillId> focusedAutofillIds = new ArrayList<>();
+        mClientParser.parse((node) -> parseNode(node, allHints, saveType, autofillIds, focusedAutofillIds));
+        mClientParser.parse((node) -> parseWebDomain(node, webDomainBuilder));
+        String webDomain = webDomainBuilder.toString();
+        AutofillId[] autofillIdsArray = autofillIds.toArray(new AutofillId[autofillIds.size()]);
+        AutofillId[] focusedIds = focusedAutofillIds.toArray(new AutofillId[focusedAutofillIds.size()]);
+        return new ClientViewMetadata(allHints, saveType.value, autofillIdsArray, focusedIds, webDomain);
+    }
+
+    private void parseWebDomain(AssistStructure.ViewNode viewNode, StringBuilder validWebDomain) {
+        String webDomain = viewNode.getWebDomain();
+        if (webDomain != null) {
+            logd("child web domain: %s", webDomain);
+            if (validWebDomain.length() > 0) {
+                if (!webDomain.equals(validWebDomain.toString())) {
+                    throw new SecurityException("Found multiple web domains: valid= "
+                            + validWebDomain + ", child=" + webDomain);
+                }
+            } else {
+                validWebDomain.append(webDomain);
+            }
+        }
+    }
+
+    private void parseNode(AssistStructure.ViewNode root, List<String> allHints,
+            MutableInt autofillSaveType, List<AutofillId> autofillIds,
+            List<AutofillId> focusedAutofillIds) {
+        String[] hints = root.getAutofillHints();
+        if (hints != null) {
+            for (String hint : hints) {
+                FieldTypeWithHeuristics fieldTypeWithHints = mFieldTypesByAutofillHint.get(hint);
+                if (fieldTypeWithHints != null && fieldTypeWithHints.fieldType != null) {
+                    allHints.add(hint);
+                    autofillSaveType.value |= fieldTypeWithHints.fieldType.getSaveInfo();
+                    autofillIds.add(root.getAutofillId());
+                }
+            }
+        }
+        if (root.isFocused()) {
+            focusedAutofillIds.add(root.getAutofillId());
+        }
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/DataCallback.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/DataCallback.java
new file mode 100644
index 0000000..e765602
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/DataCallback.java
@@ -0,0 +1,23 @@
+/*
+ * 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 com.example.android.autofill.service.data;
+
+public interface DataCallback<T> {
+    void onLoaded(T object);
+
+    void onDataNotAvailable(String msg, Object... params);
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/FakeAutofillDataBuilder.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/FakeAutofillDataBuilder.java
new file mode 100644
index 0000000..67ee880
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/FakeAutofillDataBuilder.java
@@ -0,0 +1,75 @@
+/*
+ * 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 com.example.android.autofill.service.data;
+
+import com.example.android.autofill.service.AutofillHints;
+import com.example.android.autofill.service.model.AutofillDataset;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+import java.util.UUID;
+
+public class FakeAutofillDataBuilder implements AutofillDataBuilder {
+    private final List<FieldTypeWithHeuristics> mFieldTypesWithHints;
+    private final String mPackageName;
+    private final int mSeed;
+
+    public FakeAutofillDataBuilder(List<FieldTypeWithHeuristics> fieldTypesWithHints,
+            String packageName, int seed) {
+        mFieldTypesWithHints = fieldTypesWithHints;
+        mSeed = seed;
+        mPackageName = packageName;
+    }
+
+    @Override
+    public List<DatasetWithFilledAutofillFields> buildDatasetsByPartition(int datasetNumber) {
+        ImmutableList.Builder<DatasetWithFilledAutofillFields> listBuilder =
+                new ImmutableList.Builder<>();
+        for (int partition : AutofillHints.PARTITIONS) {
+            AutofillDataset autofillDataset = new AutofillDataset(UUID.randomUUID().toString(),
+                    "dataset-" + datasetNumber + "." + partition, mPackageName);
+            DatasetWithFilledAutofillFields datasetWithFilledAutofillFields =
+                    buildCollectionForPartition(autofillDataset, partition);
+            if (datasetWithFilledAutofillFields != null &&
+                    datasetWithFilledAutofillFields.filledAutofillFields != null &&
+                    !datasetWithFilledAutofillFields.filledAutofillFields.isEmpty()) {
+                listBuilder.add(datasetWithFilledAutofillFields);
+            }
+        }
+        return listBuilder.build();
+    }
+
+    private DatasetWithFilledAutofillFields buildCollectionForPartition(
+            AutofillDataset dataset, int partition) {
+        DatasetWithFilledAutofillFields datasetWithFilledAutofillFields =
+                new DatasetWithFilledAutofillFields();
+        datasetWithFilledAutofillFields.autofillDataset = dataset;
+        for (FieldTypeWithHeuristics fieldTypeWithHeuristics : mFieldTypesWithHints) {
+            if (AutofillHints.matchesPartition(
+                    fieldTypeWithHeuristics.getFieldType().getPartition(), partition)) {
+                FilledAutofillField fakeField =
+                        AutofillHints.generateFakeField(fieldTypeWithHeuristics, mPackageName,
+                                mSeed, dataset.getId());
+                datasetWithFilledAutofillFields.add(fakeField);
+            }
+        }
+        return datasetWithFilledAutofillFields;
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/DatasetAdapter.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/DatasetAdapter.java
new file mode 100644
index 0000000..6f7e603
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/DatasetAdapter.java
@@ -0,0 +1,189 @@
+/*
+ * 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 com.example.android.autofill.service.data.adapter;
+
+import android.app.assist.AssistStructure;
+import android.content.IntentSender;
+import android.service.autofill.Dataset;
+import android.util.MutableBoolean;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import com.example.android.autofill.service.AutofillHints;
+import com.example.android.autofill.service.ClientParser;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+import static com.example.android.autofill.service.util.Util.indexOf;
+import static com.example.android.autofill.service.util.Util.logv;
+import static com.example.android.autofill.service.util.Util.logw;
+import static java.util.stream.Collectors.toMap;
+
+public class DatasetAdapter {
+    private final ClientParser mClientParser;
+
+    public DatasetAdapter(ClientParser clientParser) {
+        mClientParser = clientParser;
+    }
+
+    /**
+     * Wraps autofill data in a {@link Dataset} object which can then be sent back to the client.
+     */
+    public Dataset buildDataset(HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint,
+            DatasetWithFilledAutofillFields datasetWithFilledAutofillFields,
+            RemoteViews remoteViews) {
+        return buildDataset(fieldTypesByAutofillHint, datasetWithFilledAutofillFields, remoteViews,
+                null);
+    }
+
+    public Dataset buildDatasetForFocusedNode(FilledAutofillField filledAutofillField,
+            FieldType fieldType, RemoteViews remoteViews) {
+        Dataset.Builder datasetBuilder = new Dataset.Builder(remoteViews);
+        boolean setAtLeastOneValue = bindDatasetToFocusedNode(filledAutofillField,
+                fieldType, datasetBuilder);
+        if (!setAtLeastOneValue) {
+            return null;
+        }
+        return datasetBuilder.build();
+    }
+
+    /**
+     * Wraps autofill data in a {@link Dataset} object with an IntentSender, which can then be
+     * sent back to the client.
+     */
+    public Dataset buildDataset(HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint,
+            DatasetWithFilledAutofillFields datasetWithFilledAutofillFields,
+            RemoteViews remoteViews, IntentSender intentSender) {
+        Dataset.Builder datasetBuilder = new Dataset.Builder(remoteViews);
+        if (intentSender != null) {
+            datasetBuilder.setAuthentication(intentSender);
+        }
+        boolean setAtLeastOneValue = bindDataset(fieldTypesByAutofillHint,
+                datasetWithFilledAutofillFields, datasetBuilder);
+        if (!setAtLeastOneValue) {
+            return null;
+        }
+        return datasetBuilder.build();
+    }
+
+    /**
+     * Build an autofill {@link Dataset} using saved data and the client's AssistStructure.
+     */
+    private boolean bindDataset(HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint,
+            DatasetWithFilledAutofillFields datasetWithFilledAutofillFields,
+            Dataset.Builder datasetBuilder) {
+        MutableBoolean setValueAtLeastOnce = new MutableBoolean(false);
+        Map<String, FilledAutofillField> filledAutofillFieldsByTypeName =
+                datasetWithFilledAutofillFields.filledAutofillFields.stream()
+                        .collect(toMap(FilledAutofillField::getFieldTypeName, Function.identity()));
+        mClientParser.parse((node) ->
+                parseAutofillFields(node, fieldTypesByAutofillHint, filledAutofillFieldsByTypeName,
+                        datasetBuilder, setValueAtLeastOnce)
+        );
+        return setValueAtLeastOnce.value;
+    }
+
+    private boolean bindDatasetToFocusedNode(FilledAutofillField field,
+            FieldType fieldType, Dataset.Builder builder) {
+        MutableBoolean setValueAtLeastOnce = new MutableBoolean(false);
+        mClientParser.parse((node) -> {
+            if (node.isFocused() && node.getAutofillId() != null) {
+                bindValueToNode(node, field, builder, setValueAtLeastOnce);
+            }
+        });
+        return setValueAtLeastOnce.value;
+    }
+
+    private void parseAutofillFields(AssistStructure.ViewNode viewNode,
+            HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint,
+            Map<String, FilledAutofillField> filledAutofillFieldsByTypeName,
+            Dataset.Builder builder, MutableBoolean setValueAtLeastOnce) {
+        String[] rawHints = viewNode.getAutofillHints();
+        if (rawHints == null || rawHints.length == 0) {
+            logv("No af hints at ViewNode - %s", viewNode.getIdEntry());
+            return;
+        }
+        String fieldTypeName = AutofillHints.getFieldTypeNameFromAutofillHints(
+                fieldTypesByAutofillHint, Arrays.asList(rawHints));
+        if (fieldTypeName == null) {
+            return;
+        }
+        FilledAutofillField field = filledAutofillFieldsByTypeName.get(fieldTypeName);
+        if (field == null) {
+            return;
+        }
+        bindValueToNode(viewNode, field, builder, setValueAtLeastOnce);
+    }
+
+    void bindValueToNode(AssistStructure.ViewNode viewNode,
+            FilledAutofillField field, Dataset.Builder builder,
+            MutableBoolean setValueAtLeastOnce) {
+        AutofillId autofillId = viewNode.getAutofillId();
+        if (autofillId == null) {
+            logw("Autofill ID null for %s", viewNode.toString());
+            return;
+        }
+        int autofillType = viewNode.getAutofillType();
+        switch (autofillType) {
+            case View.AUTOFILL_TYPE_LIST:
+                CharSequence[] options = viewNode.getAutofillOptions();
+                int listValue = -1;
+                if (options != null) {
+                    listValue = indexOf(viewNode.getAutofillOptions(), field.getTextValue());
+                }
+                if (listValue != -1) {
+                    builder.setValue(autofillId, AutofillValue.forList(listValue));
+                    setValueAtLeastOnce.value = true;
+                }
+                break;
+            case View.AUTOFILL_TYPE_DATE:
+                Long dateValue = field.getDateValue();
+                if (dateValue != null) {
+                    builder.setValue(autofillId, AutofillValue.forDate(dateValue));
+                    setValueAtLeastOnce.value = true;
+                }
+                break;
+            case View.AUTOFILL_TYPE_TEXT:
+                String textValue = field.getTextValue();
+                if (textValue != null) {
+                    builder.setValue(autofillId, AutofillValue.forText(textValue));
+                    setValueAtLeastOnce.value = true;
+                }
+                break;
+            case View.AUTOFILL_TYPE_TOGGLE:
+                Boolean toggleValue = field.getToggleValue();
+                if (toggleValue != null) {
+                    builder.setValue(autofillId, AutofillValue.forToggle(toggleValue));
+                    setValueAtLeastOnce.value = true;
+                }
+                break;
+            case View.AUTOFILL_TYPE_NONE:
+            default:
+                logw("Invalid autofill type - %d", autofillType);
+                break;
+        }
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/ResponseAdapter.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/ResponseAdapter.java
new file mode 100644
index 0000000..0300fed
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/ResponseAdapter.java
@@ -0,0 +1,136 @@
+/*
+ * 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 com.example.android.autofill.service.data.adapter;
+
+import android.content.Context;
+import android.content.IntentSender;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveInfo;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import com.example.android.autofill.service.AuthActivity;
+import com.example.android.autofill.service.RemoteViewsHelper;
+import com.example.android.autofill.service.data.ClientViewMetadata;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+
+import java.util.HashMap;
+import java.util.List;
+
+public class ResponseAdapter {
+    private final Context mContext;
+    private final DatasetAdapter mDatasetAdapter;
+    private final String mPackageName;
+    private final ClientViewMetadata mClientViewMetadata;
+
+    public ResponseAdapter(Context context, ClientViewMetadata clientViewMetadata,
+            String packageName, DatasetAdapter datasetAdapter) {
+        mContext = context;
+        mClientViewMetadata = clientViewMetadata;
+        mDatasetAdapter = datasetAdapter;
+        mPackageName = packageName;
+    }
+
+    public FillResponse buildResponseForFocusedNode(String datasetName, FilledAutofillField field,
+            FieldType fieldType) {
+        FillResponse.Builder responseBuilder = new FillResponse.Builder();
+        RemoteViews remoteViews = RemoteViewsHelper.viewsWithNoAuth(
+                mPackageName, datasetName);
+        Dataset dataset = mDatasetAdapter.buildDatasetForFocusedNode(field, fieldType, remoteViews);
+        if (dataset != null) {
+            responseBuilder.addDataset(dataset);
+            return responseBuilder.build();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Wraps autofill data in a Response object (essentially a series of Datasets) which can then
+     * be sent back to the client View.
+     */
+    public FillResponse buildResponse(HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint,
+            List<DatasetWithFilledAutofillFields> datasets, boolean datasetAuth) {
+        FillResponse.Builder responseBuilder = new FillResponse.Builder();
+        if (datasets != null) {
+            for (DatasetWithFilledAutofillFields datasetWithFilledAutofillFields : datasets) {
+                if (datasetWithFilledAutofillFields != null) {
+                    Dataset dataset;
+                    String datasetName = datasetWithFilledAutofillFields.autofillDataset
+                            .getDatasetName();
+                    if (datasetAuth) {
+                        IntentSender intentSender = AuthActivity.getAuthIntentSenderForDataset(
+                                mContext, datasetName);
+                        RemoteViews remoteViews = RemoteViewsHelper.viewsWithAuth(
+                                mPackageName, datasetName);
+                        dataset = mDatasetAdapter.buildDataset(fieldTypesByAutofillHint,
+                                datasetWithFilledAutofillFields, remoteViews, intentSender);
+                    } else {
+                        RemoteViews remoteViews = RemoteViewsHelper.viewsWithNoAuth(
+                                mPackageName, datasetName);
+                        dataset = mDatasetAdapter.buildDataset(fieldTypesByAutofillHint,
+                                datasetWithFilledAutofillFields, remoteViews);
+                    }
+                    if (dataset != null) {
+                        responseBuilder.addDataset(dataset);
+                    }
+                }
+            }
+        }
+        int saveType = mClientViewMetadata.getSaveType();
+        AutofillId[] autofillIds = mClientViewMetadata.getAutofillIds();
+        if (autofillIds != null && autofillIds.length > 0) {
+            SaveInfo saveInfo = new SaveInfo.Builder(saveType, autofillIds).build();
+            responseBuilder.setSaveInfo(saveInfo);
+            return responseBuilder.build();
+        } else {
+            return null;
+        }
+    }
+
+    public FillResponse buildResponse(IntentSender sender, RemoteViews remoteViews) {
+        FillResponse.Builder responseBuilder = new FillResponse.Builder();
+        int saveType = mClientViewMetadata.getSaveType();
+        AutofillId[] autofillIds = mClientViewMetadata.getAutofillIds();
+        if (autofillIds != null && autofillIds.length > 0) {
+            SaveInfo saveInfo = new SaveInfo.Builder(saveType, autofillIds).build();
+            responseBuilder.setSaveInfo(saveInfo);
+            responseBuilder.setAuthentication(autofillIds, sender, remoteViews);
+            return responseBuilder.build();
+        } else {
+            return null;
+        }
+    }
+
+    public FillResponse buildManualResponse(IntentSender sender, RemoteViews remoteViews) {
+        FillResponse.Builder responseBuilder = new FillResponse.Builder();
+        int saveType = mClientViewMetadata.getSaveType();
+        AutofillId[] focusedIds = mClientViewMetadata.getFocusedIds();
+        if (focusedIds != null && focusedIds.length > 0) {
+            SaveInfo saveInfo = new SaveInfo.Builder(saveType, focusedIds).build();
+            return responseBuilder.setSaveInfo(saveInfo)
+                    .setAuthentication(focusedIds, sender, remoteViews)
+                    .build();
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/AutofillDataSource.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/AutofillDataSource.java
new file mode 100644
index 0000000..e3579dd
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/AutofillDataSource.java
@@ -0,0 +1,75 @@
+/*
+ * 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 com.example.android.autofill.service.data.source;
+
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.example.android.autofill.service.model.ResourceIdHeuristic;
+
+import java.util.HashMap;
+import java.util.List;
+
+public interface AutofillDataSource {
+
+    /**
+     * Asynchronously gets saved list of {@link DatasetWithFilledAutofillFields} that contains some
+     * objects that can autofill fields with these {@code autofillHints}.
+     */
+    void getAutofillDatasets(List<String> allAutofillHints,
+            DataCallback<List<DatasetWithFilledAutofillFields>> datasetsCallback);
+
+    void getAllAutofillDatasets(
+            DataCallback<List<DatasetWithFilledAutofillFields>> datasetsCallback);
+
+    /**
+     * Asynchronously gets a saved {@link DatasetWithFilledAutofillFields} for a specific
+     * {@code datasetName} that contains some objects that can autofill fields with these
+     * {@code autofillHints}.
+     */
+    void getAutofillDataset(List<String> allAutofillHints,
+            String datasetName, DataCallback<DatasetWithFilledAutofillFields> datasetsCallback);
+
+    /**
+     * Stores a collection of Autofill fields.
+     */
+    void saveAutofillDatasets(List<DatasetWithFilledAutofillFields>
+            datasetsWithFilledAutofillFields);
+
+    void saveResourceIdHeuristic(ResourceIdHeuristic resourceIdHeuristic);
+
+    /**
+     * Gets all autofill field types.
+     */
+    void getFieldTypes(DataCallback<List<FieldTypeWithHeuristics>> fieldTypesCallback);
+
+    /**
+     * Gets all autofill field types.
+     */
+    void getFieldType(String typeName, DataCallback<FieldType> fieldTypeCallback);
+
+    void getFieldTypeByAutofillHints(
+            DataCallback<HashMap<String, FieldTypeWithHeuristics>> fieldTypeMapCallback);
+
+    void getFilledAutofillField(String datasetId, String fieldTypeName, DataCallback<FilledAutofillField> fieldCallback);
+
+    /**
+     * Clears all data.
+     */
+    void clear();
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DalService.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DalService.java
new file mode 100644
index 0000000..b9cf695
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DalService.java
@@ -0,0 +1,31 @@
+/*
+ * 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 com.example.android.autofill.service.data.source;
+
+import com.example.android.autofill.service.model.DalCheck;
+
+import retrofit2.Call;
+import retrofit2.http.GET;
+import retrofit2.http.Query;
+
+public interface DalService {
+    @GET("/v1/assetlinks:check")
+    Call<DalCheck> check(@Query("source.web.site") String webDomain,
+            @Query("relation") String permission,
+            @Query("target.android_app.package_name") String packageName,
+            @Query("target.android_app.certificate.sha256_fingerprint") String fingerprint);
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DefaultFieldTypesSource.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DefaultFieldTypesSource.java
new file mode 100644
index 0000000..22cbead
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DefaultFieldTypesSource.java
@@ -0,0 +1,25 @@
+/*
+ * 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 com.example.android.autofill.service.data.source;
+
+import com.example.android.autofill.service.model.DefaultFieldTypeWithHints;
+
+import java.util.List;
+
+public interface DefaultFieldTypesSource {
+    List<DefaultFieldTypeWithHints> getDefaultFieldTypes();
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DigitalAssetLinksDataSource.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DigitalAssetLinksDataSource.java
new file mode 100644
index 0000000..00661a7
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DigitalAssetLinksDataSource.java
@@ -0,0 +1,40 @@
+/*
+ * 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 com.example.android.autofill.service.data.source;
+
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.model.DalCheck;
+import com.example.android.autofill.service.model.DalInfo;
+
+import static com.example.android.autofill.service.util.Util.DalCheckRequirement;
+
+/**
+ * Data source for
+ * <a href="https://developers.google.com/digital-asset-links/">Digital Asset Links</a>.
+ */
+public interface DigitalAssetLinksDataSource {
+
+    /**
+     * Checks if the association between a web domain and a package is valid.
+     */
+    void checkValid(DalCheckRequirement dalCheckRequirement, DalInfo dalInfo,
+            DataCallback<DalCheck> dalCheckCallback);
+
+    /**
+     * Clears all cached data.
+     */
+    void clear();
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/PackageVerificationDataSource.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/PackageVerificationDataSource.java
new file mode 100644
index 0000000..7e271e0
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/PackageVerificationDataSource.java
@@ -0,0 +1,36 @@
+/*
+ * 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 com.example.android.autofill.service.data.source;
+
+public interface PackageVerificationDataSource {
+
+    /**
+     * Verifies that the signatures in the passed {@code Context} match what is currently in
+     * storage. If there are no current signatures in storage for this packageName, it will store
+     * the signatures from the passed {@code Context}.
+     *
+     * @return {@code true} if signatures for this packageName are not currently in storage
+     * or if the signatures in the passed {@code Context} match what is currently in storage;
+     * {@code false} if the signatures in the passed {@code Context} do not match what is
+     * currently in storage or if an {@code Exception} was thrown while generating the signatures.
+     */
+    boolean putPackageSignatures(String packageName);
+
+    /**
+     * Clears all signature data currently in storage.
+     */
+    void clear();
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/DefaultFieldTypesLocalJsonSource.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/DefaultFieldTypesLocalJsonSource.java
new file mode 100644
index 0000000..d403f32
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/DefaultFieldTypesLocalJsonSource.java
@@ -0,0 +1,68 @@
+/*
+ * 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 com.example.android.autofill.service.data.source.local;
+
+import android.content.res.Resources;
+
+import com.example.android.autofill.service.R;
+import com.example.android.autofill.service.data.source.DefaultFieldTypesSource;
+import com.example.android.autofill.service.model.DefaultFieldTypeWithHints;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.lang.reflect.Type;
+import java.util.List;
+
+import static com.example.android.autofill.service.util.Util.loge;
+
+public class DefaultFieldTypesLocalJsonSource implements DefaultFieldTypesSource {
+    private static DefaultFieldTypesLocalJsonSource sInstance;
+
+    private final Resources mResources;
+    private final Gson mGson;
+
+    private DefaultFieldTypesLocalJsonSource(Resources resources, Gson gson) {
+        mResources = resources;
+        mGson = gson;
+    }
+
+    public static DefaultFieldTypesLocalJsonSource getInstance(Resources resources, Gson gson) {
+        if (sInstance == null) {
+            sInstance = new DefaultFieldTypesLocalJsonSource(resources, gson);
+        }
+        return sInstance;
+    }
+
+    @Override
+    public List<DefaultFieldTypeWithHints> getDefaultFieldTypes() {
+        Type fieldTypeListType =  TypeToken.getParameterized(List.class,
+                DefaultFieldTypeWithHints.class).getType();
+        InputStream is = mResources.openRawResource(R.raw.default_field_types);
+        List<DefaultFieldTypeWithHints> fieldTypes = null;
+        try(Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
+            fieldTypes = mGson.fromJson(reader, fieldTypeListType);
+        } catch (IOException e) {
+            loge(e, "Exception during deserialization of FieldTypes.");
+        }
+        return fieldTypes;
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/DigitalAssetLinksRepository.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/DigitalAssetLinksRepository.java
new file mode 100644
index 0000000..6303a1e
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/DigitalAssetLinksRepository.java
@@ -0,0 +1,159 @@
+/*
+ * 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 com.example.android.autofill.service.data.source.local;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.support.annotation.NonNull;
+
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.data.source.DalService;
+import com.example.android.autofill.service.data.source.DigitalAssetLinksDataSource;
+import com.example.android.autofill.service.model.DalCheck;
+import com.example.android.autofill.service.model.DalInfo;
+import com.example.android.autofill.service.util.SecurityHelper;
+import com.google.common.net.InternetDomainName;
+
+import java.util.HashMap;
+
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+
+import static com.example.android.autofill.service.util.Util.DalCheckRequirement;
+import static com.example.android.autofill.service.util.Util.DalCheckRequirement.AllUrls;
+import static com.example.android.autofill.service.util.Util.DalCheckRequirement.Disabled;
+import static com.example.android.autofill.service.util.Util.DalCheckRequirement.LoginOnly;
+import static com.example.android.autofill.service.util.Util.logd;
+
+
+/**
+ * Singleton repository that caches the result of Digital Asset Links checks.
+ */
+public class DigitalAssetLinksRepository implements DigitalAssetLinksDataSource {
+    private static final String DAL_BASE_URL = "https://digitalassetlinks.googleapis.com";
+    private static final String PERMISSION_GET_LOGIN_CREDS = "common.get_login_creds";
+    private static final String PERMISSION_HANDLE_ALL_URLS = "common.handle_all_urls";
+    private static DigitalAssetLinksRepository sInstance;
+
+    private final PackageManager mPackageManager;
+    private final DalService mDalService;
+    private final HashMap<DalInfo, DalCheck> mCache;
+
+    private DigitalAssetLinksRepository(PackageManager packageManager) {
+        mPackageManager = packageManager;
+        mCache = new HashMap<>();
+        mDalService = new Retrofit.Builder()
+                .baseUrl(DAL_BASE_URL)
+                .build()
+                .create(DalService.class);
+    }
+
+    public static DigitalAssetLinksRepository getInstance(PackageManager packageManager) {
+        if (sInstance == null) {
+            sInstance = new DigitalAssetLinksRepository(packageManager);
+        }
+        return sInstance;
+    }
+
+    public static String getCanonicalDomain(String domain) {
+        InternetDomainName idn = InternetDomainName.from(domain);
+        while (idn != null && !idn.isTopPrivateDomain()) {
+            idn = idn.parent();
+        }
+        return idn == null ? null : idn.toString();
+    }
+
+    @Override
+    public void clear() {
+        mCache.clear();
+    }
+
+    public void checkValid(DalCheckRequirement dalCheckRequirement, DalInfo dalInfo,
+            DataCallback<DalCheck> dalCheckDataCallback) {
+        if (dalCheckRequirement.equals(Disabled)) {
+            DalCheck dalCheck = new DalCheck();
+            dalCheck.linked = true;
+            dalCheckDataCallback.onLoaded(dalCheck);
+            return;
+        }
+
+        DalCheck dalCheck = mCache.get(dalInfo);
+        if (dalCheck != null) {
+            dalCheckDataCallback.onLoaded(dalCheck);
+            return;
+        }
+        String packageName = dalInfo.getPackageName();
+        String webDomain = dalInfo.getWebDomain();
+
+        final String fingerprint;
+        try {
+            PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
+                    PackageManager.GET_SIGNATURES);
+            fingerprint = SecurityHelper.getFingerprint(packageInfo, packageName);
+        } catch (Exception e) {
+            dalCheckDataCallback.onDataNotAvailable("Error getting fingerprint for %s",
+                    packageName);
+            return;
+        }
+        logd("validating domain %s for pkg %s and fingerprint %s.", webDomain,
+                packageName, fingerprint);
+        mDalService.check(webDomain, PERMISSION_GET_LOGIN_CREDS, packageName, fingerprint).enqueue(
+                new Callback<DalCheck>() {
+                    @Override
+                    public void onResponse(@NonNull Call<DalCheck> call,
+                            @NonNull Response<DalCheck> response) {
+                        DalCheck dalCheck = response.body();
+                        if (dalCheck == null || !dalCheck.linked) {
+                            // get_login_creds check failed, so try handle_all_urls check
+                            if (dalCheckRequirement.equals(LoginOnly)) {
+                                dalCheckDataCallback.onDataNotAvailable(
+                                        "DAL: Login creds check failed.");
+                            } else if (dalCheckRequirement.equals(AllUrls)) {
+                                mDalService.check(webDomain, PERMISSION_HANDLE_ALL_URLS,
+                                        packageName, fingerprint).enqueue(new Callback<DalCheck>() {
+                                    @Override
+                                    public void onResponse(@NonNull Call<DalCheck> call,
+                                            @NonNull Response<DalCheck> response) {
+                                        DalCheck dalCheck = response.body();
+                                        mCache.put(dalInfo, dalCheck);
+                                        dalCheckDataCallback.onLoaded(dalCheck);
+                                    }
+
+                                    @Override
+                                    public void onFailure(@NonNull Call<DalCheck> call,
+                                            @NonNull Throwable t) {
+                                        dalCheckDataCallback.onDataNotAvailable(t.getMessage());
+                                    }
+                                });
+                            }
+                        } else {
+                            // get_login_creds check succeeded, so we're finished.
+                            mCache.put(dalInfo, dalCheck);
+                            dalCheckDataCallback.onLoaded(dalCheck);
+                        }
+                    }
+
+                    @Override
+                    public void onFailure(@NonNull Call<DalCheck> call, @NonNull Throwable t) {
+                        // get_login_creds check failed, so try handle_all_urls check.
+                        mDalService.check(webDomain, PERMISSION_HANDLE_ALL_URLS, packageName,
+                                fingerprint);
+                    }
+                });
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/LocalAutofillDataSource.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/LocalAutofillDataSource.java
new file mode 100644
index 0000000..e58e18b
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/LocalAutofillDataSource.java
@@ -0,0 +1,257 @@
+/*
+ * 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 com.example.android.autofill.service.data.source.local;
+
+import android.content.SharedPreferences;
+import android.service.autofill.Dataset;
+
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.data.source.AutofillDataSource;
+import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
+import com.example.android.autofill.service.model.AutofillDataset;
+import com.example.android.autofill.service.model.AutofillHint;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.example.android.autofill.service.model.ResourceIdHeuristic;
+import com.example.android.autofill.service.util.AppExecutors;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.example.android.autofill.service.util.Util.logw;
+
+public class LocalAutofillDataSource implements AutofillDataSource {
+    public static final String SHARED_PREF_KEY = "com.example.android.autofill"
+            + ".service.datasource.LocalAutofillDataSource";
+    private static final String DATASET_NUMBER_KEY = "datasetNumber";
+    private static final Object sLock = new Object();
+
+    private static LocalAutofillDataSource sInstance;
+
+    private final AutofillDao mAutofillDao;
+    private final SharedPreferences mSharedPreferences;
+    private final AppExecutors mAppExecutors;
+
+    private LocalAutofillDataSource(SharedPreferences sharedPreferences, AutofillDao autofillDao,
+            AppExecutors appExecutors) {
+        mSharedPreferences = sharedPreferences;
+        mAutofillDao = autofillDao;
+        mAppExecutors = appExecutors;
+    }
+
+    public static LocalAutofillDataSource getInstance(SharedPreferences sharedPreferences,
+            AutofillDao autofillDao, AppExecutors appExecutors) {
+        synchronized (sLock) {
+            if (sInstance == null) {
+                sInstance = new LocalAutofillDataSource(sharedPreferences, autofillDao,
+                        appExecutors);
+            }
+            return sInstance;
+        }
+    }
+
+    public static void clearInstance() {
+        synchronized (sLock) {
+            sInstance = null;
+        }
+    }
+
+    @Override
+    public void getAutofillDatasets(List<String> allAutofillHints,
+            DataCallback<List<DatasetWithFilledAutofillFields>> datasetsCallback) {
+        mAppExecutors.diskIO().execute(() -> {
+            final List<String> typeNames = getFieldTypesForAutofillHints(allAutofillHints)
+                    .stream()
+                    .map(FieldTypeWithHeuristics::getFieldType)
+                    .map(FieldType::getTypeName)
+                    .collect(Collectors.toList());
+            List<DatasetWithFilledAutofillFields> datasetsWithFilledAutofillFields =
+                    mAutofillDao.getDatasets(typeNames);
+            mAppExecutors.mainThread().execute(() ->
+                    datasetsCallback.onLoaded(datasetsWithFilledAutofillFields)
+            );
+        });
+    }
+
+    @Override
+    public void getAllAutofillDatasets(
+            DataCallback<List<DatasetWithFilledAutofillFields>> datasetsCallback) {
+        mAppExecutors.diskIO().execute(() -> {
+            List<DatasetWithFilledAutofillFields> datasetsWithFilledAutofillFields =
+                    mAutofillDao.getAllDatasets();
+            mAppExecutors.mainThread().execute(() ->
+                    datasetsCallback.onLoaded(datasetsWithFilledAutofillFields)
+            );
+        });
+    }
+
+    @Override
+    public void getAutofillDataset(List<String> allAutofillHints, String datasetName,
+            DataCallback<DatasetWithFilledAutofillFields> datasetsCallback) {
+        mAppExecutors.diskIO().execute(() -> {
+            // Room does not support TypeConverters for collections.
+            List<DatasetWithFilledAutofillFields> autofillDatasetFields =
+                    mAutofillDao.getDatasetsWithName(allAutofillHints, datasetName);
+            if (autofillDatasetFields != null && !autofillDatasetFields.isEmpty()) {
+                if (autofillDatasetFields.size() > 1) {
+                    logw("More than 1 dataset with name %s", datasetName);
+                }
+                DatasetWithFilledAutofillFields dataset = autofillDatasetFields.get(0);
+
+                mAppExecutors.mainThread().execute(() ->
+                        datasetsCallback.onLoaded(dataset)
+                );
+            } else {
+                mAppExecutors.mainThread().execute(() ->
+                        datasetsCallback.onDataNotAvailable("No data found.")
+                );
+            }
+        });
+    }
+
+
+    @Override
+    public void saveAutofillDatasets(List<DatasetWithFilledAutofillFields>
+            datasetsWithFilledAutofillFields) {
+        mAppExecutors.diskIO().execute(() -> {
+            for (DatasetWithFilledAutofillFields datasetWithFilledAutofillFields :
+                    datasetsWithFilledAutofillFields) {
+                List<FilledAutofillField> filledAutofillFields =
+                        datasetWithFilledAutofillFields.filledAutofillFields;
+                AutofillDataset autofillDataset = datasetWithFilledAutofillFields.autofillDataset;
+                mAutofillDao.insertAutofillDataset(autofillDataset);
+                mAutofillDao.insertFilledAutofillFields(filledAutofillFields);
+            }
+        });
+        incrementDatasetNumber();
+    }
+
+    @Override
+    public void saveResourceIdHeuristic(ResourceIdHeuristic resourceIdHeuristic) {
+        mAppExecutors.diskIO().execute(() -> {
+            mAutofillDao.insertResourceIdHeuristic(resourceIdHeuristic);
+        });
+    }
+
+    @Override
+    public void getFieldTypes(DataCallback<List<FieldTypeWithHeuristics>> fieldTypesCallback) {
+        mAppExecutors.diskIO().execute(() -> {
+            List<FieldTypeWithHeuristics> fieldTypeWithHints = mAutofillDao.getFieldTypesWithHints();
+            mAppExecutors.mainThread().execute(() -> {
+                if (fieldTypeWithHints != null) {
+                    fieldTypesCallback.onLoaded(fieldTypeWithHints);
+                } else {
+                    fieldTypesCallback.onDataNotAvailable("Field Types not found.");
+                }
+            });
+        });
+    }
+
+    @Override
+    public void getFieldTypeByAutofillHints(
+            DataCallback<HashMap<String, FieldTypeWithHeuristics>> fieldTypeMapCallback) {
+        mAppExecutors.diskIO().execute(() -> {
+            HashMap<String, FieldTypeWithHeuristics> hintMap = getFieldTypeByAutofillHints();
+            mAppExecutors.mainThread().execute(() -> {
+                if (hintMap != null) {
+                    fieldTypeMapCallback.onLoaded(hintMap);
+                } else {
+                    fieldTypeMapCallback.onDataNotAvailable("FieldTypes not found");
+                }
+            });
+        });
+    }
+
+    @Override
+    public void getFilledAutofillField(String datasetId, String fieldTypeName, DataCallback<FilledAutofillField> fieldCallback) {
+        mAppExecutors.diskIO().execute(() -> {
+            FilledAutofillField filledAutofillField = mAutofillDao.getFilledAutofillField(datasetId, fieldTypeName);
+            mAppExecutors.mainThread().execute(() -> {
+                fieldCallback.onLoaded(filledAutofillField);
+            });
+        });
+    }
+
+    @Override
+    public void getFieldType(String fieldTypeName, DataCallback<FieldType> fieldTypeCallback) {
+        mAppExecutors.diskIO().execute(() -> {
+            FieldType fieldType = mAutofillDao.getFieldType(fieldTypeName);
+            mAppExecutors.mainThread().execute(() -> {
+                fieldTypeCallback.onLoaded(fieldType);
+            });
+        });
+    }
+
+    public void getAutofillDatasetWithId(String datasetId,
+            DataCallback<DatasetWithFilledAutofillFields> callback) {
+        mAppExecutors.diskIO().execute(() -> {
+            DatasetWithFilledAutofillFields dataset =
+                    mAutofillDao.getAutofillDatasetWithId(datasetId);
+            mAppExecutors.mainThread().execute(() -> {
+                callback.onLoaded(dataset);
+            });
+        });
+    }
+
+    private HashMap<String, FieldTypeWithHeuristics> getFieldTypeByAutofillHints() {
+        HashMap<String, FieldTypeWithHeuristics> hintMap = new HashMap<>();
+        List<FieldTypeWithHeuristics> fieldTypeWithHints =
+                mAutofillDao.getFieldTypesWithHints();
+        if (fieldTypeWithHints != null) {
+            for (FieldTypeWithHeuristics fieldType : fieldTypeWithHints) {
+                for (AutofillHint hint : fieldType.autofillHints) {
+                    hintMap.put(hint.mAutofillHint, fieldType);
+                }
+            }
+            return hintMap;
+        } else {
+            return null;
+        }
+    }
+
+    private List<FieldTypeWithHeuristics> getFieldTypesForAutofillHints(List<String> autofillHints) {
+        return mAutofillDao.getFieldTypesForAutofillHints(autofillHints);
+    }
+
+    @Override
+    public void clear() {
+        mAppExecutors.diskIO().execute(() -> {
+            mAutofillDao.clearAll();
+            mSharedPreferences.edit().putInt(DATASET_NUMBER_KEY, 0).apply();
+        });
+    }
+
+    /**
+     * For simplicity, {@link Dataset}s will be named in the form {@code dataset-X.P} where
+     * {@code X} means this was the Xth group of datasets saved, and {@code P} refers to the dataset
+     * partition number. This method returns the appropriate {@code X}.
+     */
+    public int getDatasetNumber() {
+        return mSharedPreferences.getInt(DATASET_NUMBER_KEY, 0);
+    }
+
+    /**
+     * Every time a dataset is saved, this should be called to increment the dataset number.
+     * (only important for this service's dataset naming scheme).
+     */
+    private void incrementDatasetNumber() {
+        mSharedPreferences.edit().putInt(DATASET_NUMBER_KEY, getDatasetNumber() + 1).apply();
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/SharedPrefsPackageVerificationRepository.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/SharedPrefsPackageVerificationRepository.java
new file mode 100644
index 0000000..0d43c1f
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/SharedPrefsPackageVerificationRepository.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 com.example.android.autofill.service.data.source.local;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+import com.example.android.autofill.service.data.source.PackageVerificationDataSource;
+import com.example.android.autofill.service.util.SecurityHelper;
+
+import static com.example.android.autofill.service.util.Util.logd;
+import static com.example.android.autofill.service.util.Util.logw;
+
+public class SharedPrefsPackageVerificationRepository implements PackageVerificationDataSource {
+
+    private static final String SHARED_PREF_KEY = "com.example.android.autofill.service"
+            + ".datasource.PackageVerificationDataSource";
+    private static PackageVerificationDataSource sInstance;
+
+    private final SharedPreferences mSharedPrefs;
+    private final Context mContext;
+
+    private SharedPrefsPackageVerificationRepository(Context context) {
+        mSharedPrefs = context.getApplicationContext()
+                .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE);
+        mContext = context.getApplicationContext();
+    }
+
+    public static PackageVerificationDataSource getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new SharedPrefsPackageVerificationRepository(
+                    context.getApplicationContext());
+        }
+        return sInstance;
+    }
+
+    @Override
+    public void clear() {
+        mSharedPrefs.edit().clear().apply();
+    }
+
+    @Override
+    public boolean putPackageSignatures(String packageName) {
+        String hash;
+        try {
+            PackageManager pm = mContext.getPackageManager();
+            PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+            hash = SecurityHelper.getFingerprint(packageInfo, packageName);
+            logd("Hash for %s: %s", packageName, hash);
+        } catch (Exception e) {
+            logw(e, "Error getting hash for %s.", packageName);
+            return false;
+        }
+
+        if (!containsSignatureForPackage(packageName)) {
+            // Storage does not yet contain signature for this package name.
+            mSharedPrefs.edit().putString(packageName, hash).apply();
+            return true;
+        }
+        return containsMatchingSignatureForPackage(packageName, hash);
+    }
+
+    private boolean containsSignatureForPackage(String packageName) {
+        return mSharedPrefs.contains(packageName);
+    }
+
+    private boolean containsMatchingSignatureForPackage(String packageName,
+            String hash) {
+        return hash.equals(mSharedPrefs.getString(packageName, null));
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/dao/AutofillDao.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/dao/AutofillDao.java
new file mode 100644
index 0000000..084532c
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/dao/AutofillDao.java
@@ -0,0 +1,126 @@
+/*
+ * 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 com.example.android.autofill.service.data.source.local.dao;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+
+import com.example.android.autofill.service.model.AutofillDataset;
+import com.example.android.autofill.service.model.AutofillHint;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.example.android.autofill.service.model.ResourceIdHeuristic;
+
+import java.util.Collection;
+import java.util.List;
+
+@Dao
+public interface AutofillDao {
+    /**
+     * Fetches a list of datasets associated to autofill fields on the page.
+     *
+     * @param allAutofillHints Filtering parameter; represents all of the hints associated with
+     *                         all of the views on the page.
+     */
+    @Query("SELECT DISTINCT id, datasetName FROM FilledAutofillField, AutofillDataset" +
+            " WHERE AutofillDataset.id = FilledAutofillField.datasetId" +
+            " AND FilledAutofillField.fieldTypeName IN (:allAutofillHints)")
+    List<DatasetWithFilledAutofillFields> getDatasets(List<String> allAutofillHints);
+
+    @Query("SELECT DISTINCT id, datasetName FROM FilledAutofillField, AutofillDataset" +
+            " WHERE AutofillDataset.id = FilledAutofillField.datasetId")
+    List<DatasetWithFilledAutofillFields> getAllDatasets();
+
+    /**
+     * Fetches a list of datasets associated to autofill fields. It should only return a dataset
+     * if that dataset has an autofill field associate with the view the user is focused on, and
+     * if that dataset's name matches the name passed in.
+     *
+     * @param fieldTypes Filtering parameter; represents all of the field types associated with
+     *                         all of the views on the page.
+     * @param datasetName      Filtering parameter; only return datasets with this name.
+     */
+    @Query("SELECT DISTINCT id, datasetName FROM FilledAutofillField, AutofillDataset" +
+            " WHERE AutofillDataset.id = FilledAutofillField.datasetId" +
+            " AND AutofillDataset.datasetName = (:datasetName)" +
+            " AND FilledAutofillField.fieldTypeName IN (:fieldTypes)")
+    List<DatasetWithFilledAutofillFields> getDatasetsWithName(
+            List<String> fieldTypes, String datasetName);
+
+    @Query("SELECT DISTINCT typeName, autofillTypes, saveInfo, partition, strictExampleSet, " +
+            "textTemplate, dateTemplate" +
+            " FROM FieldType, AutofillHint" +
+            " WHERE FieldType.typeName = AutofillHint.fieldTypeName" +
+            " UNION " +
+            "SELECT DISTINCT typeName, autofillTypes, saveInfo, partition, strictExampleSet, " +
+            "textTemplate, dateTemplate" +
+            " FROM FieldType, ResourceIdHeuristic" +
+            " WHERE FieldType.typeName = ResourceIdHeuristic.fieldTypeName")
+    List<FieldTypeWithHeuristics> getFieldTypesWithHints();
+
+    @Query("SELECT DISTINCT typeName, autofillTypes, saveInfo, partition, strictExampleSet, " +
+            "textTemplate, dateTemplate" +
+            " FROM FieldType, AutofillHint" +
+            " WHERE FieldType.typeName = AutofillHint.fieldTypeName" +
+            " AND AutofillHint.autofillHint IN (:autofillHints)" +
+            " UNION " +
+            "SELECT DISTINCT typeName, autofillTypes, saveInfo, partition, strictExampleSet, " +
+            "textTemplate, dateTemplate" +
+            " FROM FieldType, ResourceIdHeuristic" +
+            " WHERE FieldType.typeName = ResourceIdHeuristic.fieldTypeName")
+    List<FieldTypeWithHeuristics> getFieldTypesForAutofillHints(List<String> autofillHints);
+
+    @Query("SELECT DISTINCT id, datasetName FROM FilledAutofillField, AutofillDataset" +
+            " WHERE AutofillDataset.id = FilledAutofillField.datasetId" +
+            " AND AutofillDataset.id = (:datasetId)")
+    DatasetWithFilledAutofillFields getAutofillDatasetWithId(String datasetId);
+
+    @Query("SELECT * FROM FilledAutofillField" +
+            " WHERE FilledAutofillField.datasetId = (:datasetId)" +
+            " AND FilledAutofillField.fieldTypeName = (:fieldTypeName)")
+    FilledAutofillField getFilledAutofillField(String datasetId, String fieldTypeName);
+
+    @Query("SELECT * FROM FieldType" +
+            " WHERE FieldType.typeName = (:fieldTypeName)")
+    FieldType getFieldType(String fieldTypeName);
+
+    /**
+     * @param autofillFields Collection of autofill fields to be saved to the db.
+     */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertFilledAutofillFields(Collection<FilledAutofillField> autofillFields);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertAutofillDataset(AutofillDataset datasets);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertAutofillHints(List<AutofillHint> autofillHints);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertResourceIdHeuristic(ResourceIdHeuristic resourceIdHeuristic);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertFieldTypes(List<FieldType> fieldTypes);
+
+
+    @Query("DELETE FROM AutofillDataset")
+    void clearAll();
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/db/AutofillDatabase.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/db/AutofillDatabase.java
new file mode 100644
index 0000000..fe007ce
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/db/AutofillDatabase.java
@@ -0,0 +1,114 @@
+/*
+ * 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 com.example.android.autofill.service.data.source.local.db;
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.TypeConverters;
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import com.example.android.autofill.service.data.source.DefaultFieldTypesSource;
+import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
+import com.example.android.autofill.service.model.AutofillDataset;
+import com.example.android.autofill.service.model.AutofillHint;
+import com.example.android.autofill.service.model.DefaultFieldTypeWithHints;
+import com.example.android.autofill.service.model.FakeData;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.example.android.autofill.service.model.ResourceIdHeuristic;
+import com.example.android.autofill.service.util.AppExecutors;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.example.android.autofill.service.data.source.local.db.Converters.IntList;
+import static java.util.stream.Collectors.toList;
+
+@Database(entities = {
+        FilledAutofillField.class,
+        AutofillDataset.class,
+        FieldType.class,
+        AutofillHint.class,
+        ResourceIdHeuristic.class
+}, version = 1)
+@TypeConverters({Converters.class})
+public abstract class AutofillDatabase extends RoomDatabase {
+
+    private static final Object sLock = new Object();
+    private static AutofillDatabase sInstance;
+
+    public static AutofillDatabase getInstance(Context context,
+            DefaultFieldTypesSource defaultFieldTypesSource,
+            AppExecutors appExecutors) {
+        if (sInstance == null) {
+            synchronized (sLock) {
+                if (sInstance == null) {
+                    sInstance = Room.databaseBuilder(context.getApplicationContext(),
+                            AutofillDatabase.class, "AutofillSample.db")
+                            .addCallback(new RoomDatabase.Callback() {
+                                @Override
+                                public void onCreate(@NonNull SupportSQLiteDatabase db) {
+                                    appExecutors.diskIO().execute(() -> {
+                                        List<DefaultFieldTypeWithHints> fieldTypes =
+                                                defaultFieldTypesSource.getDefaultFieldTypes();
+                                        AutofillDatabase autofillDatabase =
+                                                getInstance(context, defaultFieldTypesSource,
+                                                        appExecutors);
+                                        autofillDatabase.saveDefaultFieldTypes(fieldTypes);
+                                    });
+                                }
+
+                                @Override
+                                public void onOpen(@NonNull SupportSQLiteDatabase db) {
+                                    super.onOpen(db);
+                                }
+                            })
+                            .build();
+                }
+            }
+        }
+        return sInstance;
+    }
+
+    private void saveDefaultFieldTypes(List<DefaultFieldTypeWithHints> defaultFieldTypes) {
+        List<FieldType> storedFieldTypes = new ArrayList<>();
+        List<AutofillHint> storedAutofillHints = new ArrayList<>();
+        for (DefaultFieldTypeWithHints defaultType : defaultFieldTypes) {
+            DefaultFieldTypeWithHints.DefaultFieldType defaultFieldType = defaultType.fieldType;
+            List<String> autofillHints = defaultType.autofillHints;
+            IntList autofillTypes = new IntList(defaultFieldType.autofillTypes);
+            DefaultFieldTypeWithHints.DefaultFakeData defaultFakeData = defaultType.fieldType.fakeData;
+            FakeData fakeData = new FakeData(new Converters.StringList(
+                    defaultFakeData.strictExampleSet), defaultFakeData.textTemplate,
+                    defaultFakeData.dateTemplate);
+            FieldType storedFieldType = new FieldType(defaultFieldType.typeName, autofillTypes,
+                    defaultFieldType.saveInfo, defaultFieldType.partition, fakeData);
+            storedFieldTypes.add(storedFieldType);
+            storedAutofillHints.addAll(autofillHints.stream()
+                    .map((autofillHint) -> new AutofillHint(autofillHint,
+                            storedFieldType.getTypeName())).collect(toList()));
+        }
+        AutofillDao autofillDao = autofillDao();
+        autofillDao.insertFieldTypes(storedFieldTypes);
+        autofillDao.insertAutofillHints(storedAutofillHints);
+    }
+
+    public abstract AutofillDao autofillDao();
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/db/Converters.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/db/Converters.java
new file mode 100644
index 0000000..2829f8b
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/db/Converters.java
@@ -0,0 +1,100 @@
+/*
+ * 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 com.example.android.autofill.service.data.source.local.db;
+
+import android.arch.persistence.room.TypeConverter;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * Type converter for Room database.
+ */
+public class Converters {
+
+    /**
+     * If database returns a {@link String} containing a comma delimited list of ints, this converts
+     * the {@link String} to an {@link IntList}.
+     */
+    @TypeConverter
+    public static IntList storedStringToIntList(String value) {
+        List<String> strings = Arrays.asList(value.split("\\s*,\\s*"));
+        List<Integer> ints = strings.stream().map(Integer::parseInt).collect(toList());
+        return new IntList(ints);
+    }
+
+    /**
+     * Converts the {@link IntList} back into a String containing a comma delimited list of
+     * ints.
+     */
+    @TypeConverter
+    public static String intListToStoredString(IntList list) {
+        StringBuilder stringBuilder = new StringBuilder();
+        for (Integer integer : list.ints) {
+            stringBuilder.append(integer).append(",");
+        }
+        return stringBuilder.toString();
+    }
+
+    /**
+     * If database returns a {@link String} containing a comma delimited list of Strings, this
+     * converts the {@link String} to a {@link StringList}.
+     */
+    @TypeConverter
+    public static StringList storedStringToStringList(String value) {
+        List<String> strings = Arrays.asList(value.split("\\s*,\\s*"));
+        return new StringList(strings);
+    }
+
+
+    /**
+     * Converts the {@link StringList} back into a {@link String} containing a comma delimited
+     * list of {@link String}s.
+     */
+    @TypeConverter
+    public static String stringListToStoredString(StringList list) {
+        StringBuilder stringBuilder = new StringBuilder();
+        for (String string : list.strings) {
+            stringBuilder.append(string).append(",");
+        }
+        return stringBuilder.toString();
+    }
+
+    /**
+     * Wrapper class for {@code List<Integer>} so it can work with Room type converters.
+     */
+    public static class IntList {
+        public final List<Integer> ints;
+
+        public IntList(List<Integer> ints) {
+            this.ints = ints;
+        }
+    }
+
+    /**
+     * Wrapper class for {@code List<String>} so it can work with Room type converters.
+     */
+    public static class StringList {
+        public final List<String> strings;
+
+        public StringList(List<String> ints) {
+            this.strings = ints;
+        }
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillDataset.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillDataset.java
new file mode 100644
index 0000000..6b6596c
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillDataset.java
@@ -0,0 +1,78 @@
+/*
+ * 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 com.example.android.autofill.service.model;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.support.annotation.NonNull;
+
+@Entity(primaryKeys = {"id"})
+public class AutofillDataset {
+    @NonNull
+    @ColumnInfo(name = "id")
+    private final String mId;
+
+    @NonNull
+    @ColumnInfo(name = "datasetName")
+    private final String mDatasetName;
+
+    @NonNull
+    @ColumnInfo(name = "packageName")
+    private final String mPackageName;
+
+    public AutofillDataset(@NonNull String id, @NonNull String datasetName,
+                           @NonNull String packageName) {
+        mId = id;
+        mDatasetName = datasetName;
+        mPackageName = packageName;
+    }
+
+    @NonNull
+    public String getId() {
+        return mId;
+    }
+
+    @NonNull
+    public String getDatasetName() {
+        return mDatasetName;
+    }
+
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        AutofillDataset that = (AutofillDataset) o;
+
+        if (!mId.equals(that.mId)) return false;
+        if (!mDatasetName.equals(that.mDatasetName)) return false;
+        return mPackageName.equals(that.mPackageName);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mId.hashCode();
+        result = 31 * result + mDatasetName.hashCode();
+        result = 31 * result + mPackageName.hashCode();
+        return result;
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillHint.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillHint.java
new file mode 100644
index 0000000..be4aa7d
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillHint.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 com.example.android.autofill.service.model;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.ForeignKey;
+import android.support.annotation.NonNull;
+
+@Entity(primaryKeys = {"autofillHint"}, foreignKeys = @ForeignKey(
+        entity = FieldType.class, parentColumns = "typeName", childColumns = "fieldTypeName",
+        onDelete = ForeignKey.CASCADE))
+public class AutofillHint {
+
+    @NonNull
+    @ColumnInfo(name = "autofillHint")
+    public String mAutofillHint;
+
+    @NonNull
+    @ColumnInfo(name = "fieldTypeName")
+    public String mFieldTypeName;
+
+    public AutofillHint(@NonNull String autofillHint, @NonNull String fieldTypeName) {
+        this.mAutofillHint = autofillHint;
+        this.mFieldTypeName = fieldTypeName;
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalCheck.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalCheck.java
new file mode 100644
index 0000000..ede77ba
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalCheck.java
@@ -0,0 +1,23 @@
+/*
+ * 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 com.example.android.autofill.service.model;
+
+public class DalCheck {
+    public boolean linked;
+    public String maxAge;
+    public String debugString;
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalInfo.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalInfo.java
new file mode 100644
index 0000000..44002ca
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalInfo.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.example.android.autofill.service.model;
+
+import static com.example.android.autofill.service.data.source.local.DigitalAssetLinksRepository.getCanonicalDomain;
+
+public class DalInfo {
+    private final String mWebDomain;
+    private final String mPackageName;
+
+    public DalInfo(String webDomain, String packageName) {
+        String canonicalDomain = getCanonicalDomain(webDomain);
+        final String fullDomain;
+        if (!webDomain.startsWith("http:") && !webDomain.startsWith("https:")) {
+            // Unfortunately AssistStructure.ViewNode does not tell what the domain is, so let's
+            // assume it's https
+            fullDomain = "https://" + canonicalDomain;
+        } else {
+            fullDomain = canonicalDomain;
+        }
+        mWebDomain = fullDomain;
+        mPackageName = packageName;
+    }
+
+    public String getWebDomain() {
+        return mWebDomain;
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        DalInfo dalInfo = (DalInfo) o;
+
+        if (mWebDomain != null ? !mWebDomain.equals(dalInfo.mWebDomain) :
+                dalInfo.mWebDomain != null)
+            return false;
+        return mPackageName != null ? mPackageName.equals(dalInfo.mPackageName) :
+                dalInfo.mPackageName == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mWebDomain != null ? mWebDomain.hashCode() : 0;
+        result = 31 * result + (mPackageName != null ? mPackageName.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DatasetWithFilledAutofillFields.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DatasetWithFilledAutofillFields.java
new file mode 100644
index 0000000..06b529f
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DatasetWithFilledAutofillFields.java
@@ -0,0 +1,60 @@
+/*
+ * 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 com.example.android.autofill.service.model;
+
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Relation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DatasetWithFilledAutofillFields {
+    @Embedded
+    public AutofillDataset autofillDataset;
+
+    @Relation(parentColumn = "id", entityColumn = "datasetId", entity = FilledAutofillField.class)
+    public List<FilledAutofillField> filledAutofillFields;
+
+    public void add(FilledAutofillField filledAutofillField) {
+        if (filledAutofillFields == null) {
+            this.filledAutofillFields = new ArrayList<>();
+        }
+        this.filledAutofillFields.add(filledAutofillField);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        DatasetWithFilledAutofillFields that = (DatasetWithFilledAutofillFields) o;
+
+        if (autofillDataset != null ? !autofillDataset.equals(that.autofillDataset) :
+                that.autofillDataset != null)
+            return false;
+        return filledAutofillFields != null ?
+                filledAutofillFields.equals(that.filledAutofillFields) :
+                that.filledAutofillFields == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = autofillDataset != null ? autofillDataset.hashCode() : 0;
+        result = 31 * result + (filledAutofillFields != null ? filledAutofillFields.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DefaultFieldTypeWithHints.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DefaultFieldTypeWithHints.java
new file mode 100644
index 0000000..70ad382
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DefaultFieldTypeWithHints.java
@@ -0,0 +1,73 @@
+/*
+ * 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 com.example.android.autofill.service.model;
+
+import java.util.List;
+
+/**
+ * JSON model class, representing an autofillable field type. It is called "Default" because only
+ * default field types will be included in the packaged JSON. After the JSON is initially read and
+ * written to the DB, the field types can be dynamically added, modified, and removed.
+ *<p>
+ * It contains all of the metadata about the field type. For example, if the field type is
+ * "country", this is the JSON object associated with it:
+ <pre class="prettyprint">
+ {
+     "autofillHints": [
+         "country"
+     ],
+     "fieldType": {
+         "autofillTypes": [
+             1,
+             3
+         ],
+         "fakeData": {
+             "strictExampleSet": [],
+             "textTemplate": "countryseed"
+         },
+         "partition": 1,
+         "saveInfo": 2,
+         "typeName": "country"
+     }
+ }
+ </pre>
+ */
+public class DefaultFieldTypeWithHints {
+    public DefaultFieldType fieldType;
+    public List<String> autofillHints;
+
+    public static class DefaultFieldType {
+        public String typeName;
+        public List<Integer> autofillTypes;
+        public int saveInfo;
+        public int partition;
+        public DefaultFakeData fakeData;
+    }
+
+    public static class DefaultFakeData {
+        public List<String> strictExampleSet;
+        public String textTemplate;
+        public String dateTemplate;
+
+        public DefaultFakeData(List<String> strictExampleSet, String textTemplate,
+                String dateTemplate) {
+            this.strictExampleSet = strictExampleSet;
+            this.textTemplate = textTemplate;
+            this.dateTemplate = dateTemplate;
+        }
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FakeData.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FakeData.java
new file mode 100644
index 0000000..5d0837b
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FakeData.java
@@ -0,0 +1,31 @@
+/*
+ * 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 com.example.android.autofill.service.model;
+
+import com.example.android.autofill.service.data.source.local.db.Converters;
+
+public class FakeData {
+    public Converters.StringList strictExampleSet;
+    public String textTemplate;
+    public String dateTemplate;
+
+    public FakeData(Converters.StringList strictExampleSet, String textTemplate, String dateTemplate) {
+        this.strictExampleSet = strictExampleSet;
+        this.textTemplate = textTemplate;
+        this.dateTemplate = dateTemplate;
+    }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FieldType.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FieldType.java
new file mode 100644
index 0000000..4d285f3
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FieldType.java
@@ -0,0 +1,81 @@
+/*
+ * 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 com.example.android.autofill.service.model;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Entity;
+import android.support.annotation.NonNull;
+
+import static com.example.android.autofill.service.data.source.local.db.Converters.IntList;
+
+@Entity(primaryKeys = {"typeName"})
+public class FieldType {
+    @NonNull
+    @ColumnInfo(name = "typeName")
+    private final String mTypeName;
+
+    @NonNull
+    @ColumnInfo(name = "autofillTypes")
+    private final IntList mAutofillTypes;
+
+    @NonNull
+    @ColumnInfo(name = "saveInfo")
+    private final Integer mSaveInfo;
+
+    @NonNull
+    @ColumnInfo(name = "partition")
+    private final Integer mPartition;
+
+    @NonNull
+    @Embedded
+    private final FakeData mFakeData;
+
+    public FieldType(@NonNull String typeName, @NonNull IntList autofillTypes,
+            @NonNull Integer saveInfo, @NonNull Integer partition, @NonNull FakeData fakeData) {
+        mTypeName = typeName;
+        mAutofillTypes = autofillTypes;
+        mSaveInfo = saveInfo;
+        mPartition = partition;
+        mFakeData = fakeData;
+    }
+
+    @NonNull
+    public String getTypeName() {
+        return mTypeName;
+    }
+
+    @NonNull
+    public IntList getAutofillTypes() {
+        return mAutofillTypes;
+    }
+
+    @NonNull
+    public Integer getSaveInfo() {
+        return mSaveInfo;
+    }
+
+    @NonNull
+    public Integer getPartition() {
+        return mPartition;
+    }
+
+    @NonNull
+    public FakeData getFakeData() {
+        return mFakeData;
+    }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FieldTypeWithHeuristics.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FieldTypeWithHeuristics.java
new file mode 100644
index 0000000..97a646e
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FieldTypeWithHeuristics.java
@@ -0,0 +1,45 @@
+/*
+ * 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 com.example.android.autofill.service.model;
+
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Relation;
+
+import java.util.List;
+
+public class FieldTypeWithHeuristics {
+    @Embedded
+    public FieldType fieldType;
+
+    @Relation(parentColumn = "typeName", entityColumn = "fieldTypeName", entity = AutofillHint.class)
+    public List<AutofillHint> autofillHints;
+
+    @Relation(parentColumn = "typeName", entityColumn = "fieldTypeName", entity = ResourceIdHeuristic.class)
+    public List<ResourceIdHeuristic> resourceIdHeuristics;
+
+    public FieldType getFieldType() {
+        return fieldType;
+    }
+
+    public List<AutofillHint> getAutofillHints() {
+        return autofillHints;
+    }
+
+    public List<ResourceIdHeuristic> getResourceIdHeuristics() {
+        return resourceIdHeuristics;
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/ResourceIdHeuristic.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/ResourceIdHeuristic.java
new file mode 100644
index 0000000..81c9abe
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/ResourceIdHeuristic.java
@@ -0,0 +1,47 @@
+/*
+ * 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 com.example.android.autofill.service.model;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.ForeignKey;
+import android.support.annotation.NonNull;
+
+@Entity(primaryKeys = {"resourceIdHeuristic", "packageName"}, foreignKeys = @ForeignKey(
+        entity = FieldType.class, parentColumns = "typeName", childColumns = "fieldTypeName",
+        onDelete = ForeignKey.CASCADE))
+public class ResourceIdHeuristic {
+
+    @NonNull
+    @ColumnInfo(name = "resourceIdHeuristic")
+    public String mResourceIdHeuristic;
+
+    @NonNull
+    @ColumnInfo(name = "packageName")
+    public String mPackageName;
+
+    @NonNull
+    @ColumnInfo(name = "fieldTypeName")
+    public String mFieldTypeName;
+
+    public ResourceIdHeuristic(@NonNull String resourceIdHeuristic, @NonNull String fieldTypeName,
+            @NonNull String packageName) {
+        mResourceIdHeuristic = resourceIdHeuristic;
+        mFieldTypeName = fieldTypeName;
+        mPackageName = packageName;
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java
new file mode 100644
index 0000000..2b952e7
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java
@@ -0,0 +1,195 @@
+/*
+ * 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 com.example.android.autofill.service.simple;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillRequest;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.SaveRequest;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.util.ArrayMap;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import com.example.android.autofill.service.MyAutofillService;
+import com.example.android.autofill.service.R;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A very basic {@link AutofillService} implementation that only shows dynamic-generated datasets
+ * and don't persist the saved data.
+ *
+ * <p>The goal of this class is to provide a simple autofill service implementation that is easy
+ * to understand and extend, but it should <strong>not</strong> be used as-is on real apps because
+ * it lacks fundamental security requirements such as data partitioning and package verification
+ * &mdashthese requirements are fullfilled by {@link MyAutofillService}.
+ */
+public final class BasicService extends AutofillService {
+
+    private static final String TAG = "BasicService";
+
+    /**
+     * Number of datasets sent on each request - we're simple, that value is hardcoded in our DNA!
+     */
+    private static final int NUMBER_DATASETS = 4;
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillCallback callback) {
+        Log.d(TAG, "onFillRequest()");
+
+        // Find autofillable fields
+        AssistStructure structure = getLatestAssistStructure(request);
+        Map<String, AutofillId> fields = getAutofillableFields(structure);
+        Log.d(TAG, "autofillable fields:" + fields);
+
+        if (fields.isEmpty()) {
+            toast("No autofill hints found");
+            callback.onSuccess(null);
+            return;
+        }
+
+        // Create the base response
+        FillResponse.Builder response = new FillResponse.Builder();
+
+        // 1.Add the dynamic datasets
+        String packageName = getApplicationContext().getPackageName();
+        for (int i = 1; i <= NUMBER_DATASETS; i++) {
+            Dataset.Builder dataset = new Dataset.Builder();
+            for (Entry<String, AutofillId> field : fields.entrySet()) {
+                String hint = field.getKey();
+                AutofillId id = field.getValue();
+                String value = i + "-" + hint;
+                // We're simple - our dataset values are hardcoded as "N-hint" (for example,
+                // "1-username", "2-username") and they're displayed as such, except if they're a
+                // password
+                String displayValue = hint.contains("password") ? "password for #" + i : value;
+                RemoteViews presentation = newDatasetPresentation(packageName, displayValue);
+                dataset.setValue(id, AutofillValue.forText(value), presentation);
+            }
+            response.addDataset(dataset.build());
+        }
+
+        // 2.Add save info
+        Collection<AutofillId> ids = fields.values();
+        AutofillId[] requiredIds = new AutofillId[ids.size()];
+        ids.toArray(requiredIds);
+        response.setSaveInfo(
+                // We're simple, so we're generic
+                new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, requiredIds).build());
+
+        // 3.Profit!
+        callback.onSuccess(response.build());
+    }
+
+    @Override
+    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+        Log.d(TAG, "onSaveRequest()");
+        toast("Save not supported");
+        callback.onSuccess();
+    }
+
+    /**
+     * Parses the {@link AssistStructure} representing the activity being autofilled, and returns a
+     * map of autofillable fields (represented by their autofill ids) mapped by the hint associate
+     * with them.
+     *
+     * <p>An autofillable field is a {@link ViewNode} whose {@link #getHint(ViewNode)} metho
+     */
+    @NonNull
+    private Map<String, AutofillId> getAutofillableFields(@NonNull AssistStructure structure) {
+        Map<String, AutofillId> fields = new ArrayMap<>();
+        int nodes = structure.getWindowNodeCount();
+        for (int i = 0; i < nodes; i++) {
+            ViewNode node = structure.getWindowNodeAt(i).getRootViewNode();
+            addAutofillableFields(fields, node);
+        }
+        return fields;
+    }
+
+    /**
+     * Adds any autofillable view from the {@link ViewNode} and its descendants to the map.
+     */
+    private void addAutofillableFields(@NonNull Map<String, AutofillId> fields,
+            @NonNull ViewNode node) {
+        String[] hints = node.getAutofillHints();
+        if (hints != null) {
+            // We're simple, we only care about the first hint
+            String hint = hints[0].toLowerCase();
+
+            if (hint != null) {
+                AutofillId id = node.getAutofillId();
+                if (!fields.containsKey(hint)) {
+                    Log.v(TAG, "Setting hint '" + hint + "' on " + id);
+                    fields.put(hint, id);
+                } else {
+                    Log.v(TAG, "Ignoring hint '" + hint + "' on " + id
+                            + " because it was already set");
+                }
+            }
+        }
+        int childrenSize = node.getChildCount();
+        for (int i = 0; i < childrenSize; i++) {
+            addAutofillableFields(fields, node.getChildAt(i));
+        }
+    }
+
+    /**
+     * Helper method to get the {@link AssistStructure} associated with the latest request
+     * in an autofill context.
+     */
+    @NonNull
+    static AssistStructure getLatestAssistStructure(@NonNull FillRequest request) {
+        List<FillContext> fillContexts = request.getFillContexts();
+        return fillContexts.get(fillContexts.size() - 1).getStructure();
+    }
+
+    /**
+     * Helper method to create a dataset presentation with the given text.
+     */
+    @NonNull
+    static RemoteViews newDatasetPresentation(@NonNull String packageName,
+            @NonNull CharSequence text) {
+        RemoteViews presentation =
+                new RemoteViews(packageName, R.layout.multidataset_service_list_item);
+        presentation.setTextViewText(R.id.text, text);
+        presentation.setImageViewResource(R.id.icon, R.mipmap.ic_launcher);
+        return presentation;
+    }
+
+    /**
+     * Displays a toast with the given message.
+     */
+    private void toast(@NonNull CharSequence message) {
+        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java
new file mode 100644
index 0000000..2cc6572
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java
@@ -0,0 +1,317 @@
+/*
+ * 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 com.example.android.autofill.service.simple;
+
+import static com.example.android.autofill.service.simple.BasicService.getLatestAssistStructure;
+import static com.example.android.autofill.service.simple.BasicService.newDatasetPresentation;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.content.Context;
+import android.content.IntentSender;
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.SaveRequest;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import com.example.android.autofill.service.MyAutofillService;
+import com.example.android.autofill.service.settings.MyPreferences;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A basic service that uses some rudimentary heuristics to identify fields that are not explicitly
+ * marked with autofill hints.
+ *
+ * <p>The goal of this class is to provide a simple autofill service implementation that is easy
+ * to understand and extend, but it should <strong>not</strong> be used as-is on real apps because
+ * it lacks fundamental security requirements such as data partitioning and package verification
+ * &mdashthese requirements are fullfilled by {@link MyAutofillService}.
+ */
+public class HeuristicsService extends AutofillService {
+
+    private static final String TAG = "HeuristicsService";
+
+    private boolean mAuthenticateResponses;
+    private boolean mAuthenticateDatasets;
+    private int mNumberDatasets = 4;
+
+    @Override
+    public void onConnected() {
+        super.onConnected();
+
+        // TODO(b/114236837): use its own preferences?
+        MyPreferences pref = MyPreferences.getInstance(getApplicationContext());
+        mAuthenticateResponses = pref.isResponseAuth();
+        mAuthenticateDatasets = pref.isDatasetAuth();
+        // TODO(b/114236837): get number dataset from preferences
+
+        Log.d(TAG, "onConnected(): numberDatasets=" + mNumberDatasets
+                + ", authResponses=" + mAuthenticateResponses
+                + ", authDatasets=" + mAuthenticateDatasets);
+    }
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillCallback callback) {
+        Log.d(TAG, "onFillRequest()");
+
+        // Find autofillable fields
+        AssistStructure structure = getLatestAssistStructure(request);
+        ArrayMap<String, AutofillId> fields = getAutofillableFields(structure);
+        Log.d(TAG, "autofillable fields:" + fields);
+
+        if (fields.isEmpty()) {
+            toast("No autofill hints found");
+            callback.onSuccess(null);
+            return;
+        }
+
+        // Create response...
+        FillResponse response;
+        if (mAuthenticateResponses) {
+            int size = fields.size();
+            String[] hints = new String[size];
+            AutofillId[] ids = new AutofillId[size];
+            for (int i = 0; i < size; i++) {
+                hints[i] = fields.keyAt(i);
+                ids[i] = fields.valueAt(i);
+            }
+
+            IntentSender authentication = SimpleAuthActivity.newIntentSenderForResponse(this, hints,
+                    ids, mAuthenticateDatasets);
+            RemoteViews presentation = newDatasetPresentation(getPackageName(),
+                        "Tap to auth response");
+
+            response = new FillResponse.Builder()
+                    .setAuthentication(ids, authentication, presentation).build();
+        } else {
+            response = createResponse(this, fields, mNumberDatasets,mAuthenticateDatasets);
+        }
+
+        // ... and return it
+        callback.onSuccess(response);
+    }
+
+    @Override
+    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+        Log.d(TAG, "onSaveRequest()");
+        toast("Save not supported");
+        callback.onSuccess();
+    }
+
+    /**
+     * Parses the {@link AssistStructure} representing the activity being autofilled, and returns a
+     * map of autofillable fields (represented by their autofill ids) mapped by the hint associate
+     * with them.
+     *
+     * <p>An autofillable field is a {@link ViewNode} whose {@link #getHint(ViewNode)} metho
+     */
+    @NonNull
+    private ArrayMap<String, AutofillId> getAutofillableFields(@NonNull AssistStructure structure) {
+        ArrayMap<String, AutofillId> fields = new ArrayMap<>();
+        int nodes = structure.getWindowNodeCount();
+        for (int i = 0; i < nodes; i++) {
+            ViewNode node = structure.getWindowNodeAt(i).getRootViewNode();
+            addAutofillableFields(fields, node);
+        }
+        return fields;
+    }
+
+    /**
+     * Adds any autofillable view from the {@link ViewNode} and its descendants to the map.
+     */
+    private void addAutofillableFields(@NonNull Map<String, AutofillId> fields,
+            @NonNull ViewNode node) {
+        String hint = getHint(node);
+        if (hint != null) {
+            AutofillId id = node.getAutofillId();
+            if (!fields.containsKey(hint)) {
+                Log.v(TAG, "Setting hint '" + hint + "' on " + id);
+                fields.put(hint, id);
+            } else {
+                Log.v(TAG, "Ignoring hint '" + hint + "' on " + id
+                        + " because it was already set");
+            }
+        }
+        int childrenSize = node.getChildCount();
+        for (int i = 0; i < childrenSize; i++) {
+            addAutofillableFields(fields, node.getChildAt(i));
+        }
+    }
+
+    @Nullable
+    protected String getHint(@NonNull ViewNode node) {
+
+        // First try the explicit autofill hints...
+
+        String[] hints = node.getAutofillHints();
+        if (hints != null) {
+            // We're simple, we only care about the first hint
+            return hints[0].toLowerCase();
+        }
+
+        // Then try some rudimentary heuristics based on other node properties
+
+        String viewHint = node.getHint();
+        String hint = inferHint(node, viewHint);
+        if (hint != null) {
+            Log.d(TAG, "Found hint using view hint(" + viewHint + "): " + hint);
+            return hint;
+        } else if (!TextUtils.isEmpty(viewHint)) {
+            Log.v(TAG, "No hint using view hint: " + viewHint);
+        }
+
+        String resourceId = node.getIdEntry();
+        hint = inferHint(node, resourceId);
+        if (hint != null) {
+            Log.d(TAG, "Found hint using resourceId(" + resourceId + "): " + hint);
+            return hint;
+        } else if (!TextUtils.isEmpty(resourceId)) {
+            Log.v(TAG, "No hint using resourceId: " + resourceId);
+        }
+
+        CharSequence text = node.getText();
+        CharSequence className = node.getClassName();
+        if (text != null && className != null && className.toString().contains("EditText")) {
+            hint = inferHint(node, text.toString());
+            if (hint != null) {
+                // NODE: text should not be logged, as it could contain PII
+                Log.d(TAG, "Found hint using text(" + text + "): " + hint);
+                return hint;
+            }
+        } else if (!TextUtils.isEmpty(text)) {
+            // NODE: text should not be logged, as it could contain PII
+            Log.v(TAG, "No hint using text: " + text + " and class " + className);
+        }
+        return null;
+    }
+
+    /**
+     * Uses heuristics to infer an autofill hint from a {@code string}.
+     *
+     * @return standard autofill hint, or {@code null} when it could not be inferred.
+     */
+    @Nullable
+    protected String inferHint(ViewNode node, @Nullable String string) {
+        if (string == null) return null;
+
+        string = string.toLowerCase();
+        if (string.contains("label")) {
+            Log.v(TAG, "Ignoring 'label' hint: " + string);
+            return null;
+        }
+        if (string.contains("password")) return View.AUTOFILL_HINT_PASSWORD;
+        if (string.contains("username")
+                || (string.contains("login") && string.contains("id")))
+            return View.AUTOFILL_HINT_USERNAME;
+        if (string.contains("email")) return View.AUTOFILL_HINT_EMAIL_ADDRESS;
+        if (string.contains("name")) return View.AUTOFILL_HINT_NAME;
+        if (string.contains("phone")) return View.AUTOFILL_HINT_PHONE;
+
+        // When everything else fails, return the full string - this is helpful to help app
+        // developers visualize when autofill is triggered when it shouldn't (for example, in a
+        // chat conversation window), so they can mark the root view of such activities with
+        // android:importantForAutofill=noExcludeDescendants
+        if (node.isEnabled() && node.getAutofillType() != View.AUTOFILL_TYPE_NONE) {
+            Log.v(TAG, "Falling back to " + string);
+            return string;
+        }
+        return null;
+    }
+
+    static FillResponse createResponse(@NonNull Context context,
+            @NonNull ArrayMap<String, AutofillId> fields, int numDatasets,
+            boolean authenticateDatasets) {
+        String packageName = context.getPackageName();
+        FillResponse.Builder response = new FillResponse.Builder();
+        // 1.Add the dynamic datasets
+        for (int i = 1; i <= numDatasets; i++) {
+            Dataset unlockedDataset = newUnlockedDataset(fields, packageName, i);
+            if (authenticateDatasets) {
+                Dataset.Builder lockedDataset = new Dataset.Builder();
+                for (Entry<String, AutofillId> field : fields.entrySet()) {
+                    String hint = field.getKey();
+                    AutofillId id = field.getValue();
+                    String value = i + "-" + hint;
+                    IntentSender authentication =
+                            SimpleAuthActivity.newIntentSenderForDataset(context, unlockedDataset);
+                    RemoteViews presentation = newDatasetPresentation(packageName,
+                            "Tap to auth " + value);
+                    lockedDataset.setValue(id, null, presentation)
+                            .setAuthentication(authentication);
+                }
+                response.addDataset(lockedDataset.build());
+            } else {
+                response.addDataset(unlockedDataset);
+            }
+        }
+
+        // 2.Add save info
+        Collection<AutofillId> ids = fields.values();
+        AutofillId[] requiredIds = new AutofillId[ids.size()];
+        ids.toArray(requiredIds);
+        response.setSaveInfo(
+                // We're simple, so we're generic
+                new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, requiredIds).build());
+
+        // 3.Profit!
+        return response.build();
+    }
+
+    static Dataset newUnlockedDataset(@NonNull Map<String, AutofillId> fields,
+            @NonNull String packageName, int i) {
+        Dataset.Builder dataset = new Dataset.Builder();
+        for (Entry<String, AutofillId> field : fields.entrySet()) {
+            String hint = field.getKey();
+            AutofillId id = field.getValue();
+            String value = i + "-" + hint;
+
+            // We're simple - our dataset values are hardcoded as "N-hint" (for example,
+            // "1-username", "2-username") and they're displayed as such, except if they're a
+            // password
+            String displayValue = hint.contains("password") ? "password for #" + i : value;
+            RemoteViews presentation = newDatasetPresentation(packageName, displayValue);
+            dataset.setValue(id, AutofillValue.forText(value), presentation);
+        }
+
+        return dataset.build();
+    }
+
+    /**
+     * Displays a toast with the given message.
+     */
+    private void toast(@NonNull CharSequence message) {
+        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/SimpleAuthActivity.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/SimpleAuthActivity.java
new file mode 100644
index 0000000..4ff97a7
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/SimpleAuthActivity.java
@@ -0,0 +1,117 @@
+/*
+ * 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 com.example.android.autofill.service.simple;
+
+import static android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE;
+import static android.view.autofill.AutofillManager.EXTRA_AUTHENTICATION_RESULT;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.ArrayMap;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import com.example.android.autofill.service.R;
+
+import java.util.Map.Entry;
+
+/**
+ * Activity used for autofill authentication, it simply sets the dataste upon tapping OK.
+ */
+// TODO(b/114236837): should display a small dialog, not take the full screen
+public class SimpleAuthActivity extends Activity {
+
+    private static final String EXTRA_DATASET = "dataset";
+    private static final String EXTRA_HINTS = "hints";
+    private static final String EXTRA_IDS = "ids";
+    private static final String EXTRA_AUTH_DATASETS = "auth_datasets";
+
+    private static int sPendingIntentId = 0;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.simple_service_auth_activity);
+        findViewById(R.id.yes).setOnClickListener((view) -> onYes());
+        findViewById(R.id.no).setOnClickListener((view) -> onNo());
+    }
+
+    private void onYes() {
+        Intent myIntent = getIntent();
+        Intent replyIntent = new Intent();
+        Dataset dataset = myIntent.getParcelableExtra(EXTRA_DATASET);
+        if (dataset != null) {
+            replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, dataset);
+        } else {
+            String[] hints = myIntent.getStringArrayExtra(EXTRA_HINTS);
+            Parcelable[] ids = myIntent.getParcelableArrayExtra(EXTRA_IDS);
+            boolean authenticateDatasets = myIntent.getBooleanExtra(EXTRA_AUTH_DATASETS, false);
+            int size = hints.length;
+            ArrayMap<String, AutofillId> fields = new ArrayMap<>(size);
+            for (int i = 0; i < size; i++) {
+                fields.put(hints[i], (AutofillId) ids[i]);
+            }
+            FillResponse response =
+                    HeuristicsService.createResponse(this, fields, 1, authenticateDatasets);
+            replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, response);
+
+        }
+        setResult(RESULT_OK, replyIntent);
+        finish();
+    }
+
+    private void onNo() {
+        setResult(RESULT_CANCELED);
+        finish();
+    }
+
+    public static IntentSender newIntentSenderForDataset(@NonNull Context context,
+            @NonNull Dataset dataset) {
+        return newIntentSender(context, dataset, null, null, false);
+    }
+
+    public static IntentSender newIntentSenderForResponse(@NonNull Context context,
+            @NonNull String[] hints, @NonNull AutofillId[] ids, boolean authenticateDatasets) {
+        return newIntentSender(context, null, hints, ids, authenticateDatasets);
+    }
+
+    private static IntentSender newIntentSender(@NonNull Context context,
+            @Nullable Dataset dataset, @Nullable String[] hints, @Nullable AutofillId[] ids,
+            boolean authenticateDatasets) {
+        Intent intent = new Intent(context, SimpleAuthActivity.class);
+        if (dataset != null) {
+            intent.putExtra(EXTRA_DATASET, dataset);
+        } else {
+            intent.putExtra(EXTRA_HINTS, hints);
+            intent.putExtra(EXTRA_IDS, ids);
+            intent.putExtra(EXTRA_AUTH_DATASETS, authenticateDatasets);
+        }
+
+        return PendingIntent.getActivity(context, ++sPendingIntentId, intent,
+                PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/AppExecutors.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/AppExecutors.java
new file mode 100644
index 0000000..9befaeb
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/AppExecutors.java
@@ -0,0 +1,91 @@
+/*
+ * 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 com.example.android.autofill.service.util;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * Global executor pools for the whole application.
+ * <p>
+ * Grouping tasks like this avoids the effects of task starvation (e.g. disk reads don't wait behind
+ * webservice requests).
+ */
+public class AppExecutors {
+
+    private static final int THREAD_COUNT = 3;
+
+    private final Executor diskIO;
+
+    private final Executor networkIO;
+
+    private final Executor mainThread;
+
+    @VisibleForTesting
+    AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread) {
+        this.diskIO = diskIO;
+        this.networkIO = networkIO;
+        this.mainThread = mainThread;
+    }
+
+    public AppExecutors() {
+        this(new DiskIOThreadExecutor(), Executors.newFixedThreadPool(THREAD_COUNT),
+                new MainThreadExecutor());
+    }
+
+    public Executor diskIO() {
+        return diskIO;
+    }
+
+    public Executor networkIO() {
+        return networkIO;
+    }
+
+    public Executor mainThread() {
+        return mainThread;
+    }
+
+    private static class MainThreadExecutor implements Executor {
+        private Handler mainThreadHandler = new Handler(Looper.getMainLooper());
+
+        @Override
+        public void execute(@NonNull Runnable command) {
+            mainThreadHandler.post(command);
+        }
+    }
+
+    /**
+     * Executor that runs a task on a new background thread.
+     */
+    private static class DiskIOThreadExecutor implements Executor {
+
+        private final Executor mDiskIO;
+
+        public DiskIOThreadExecutor() {
+            mDiskIO = Executors.newSingleThreadExecutor();
+        }
+
+        @Override
+        public void execute(@NonNull Runnable command) {
+            mDiskIO.execute(command);
+        }
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/SecurityHelper.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/SecurityHelper.java
new file mode 100644
index 0000000..5311a5f
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/SecurityHelper.java
@@ -0,0 +1,78 @@
+/*
+ * 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 com.example.android.autofill.service.util;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+/**
+ * Helper class for security checks.
+ */
+public final class SecurityHelper {
+
+    private SecurityHelper() {
+        throw new UnsupportedOperationException("Provides static methods only.");
+    }
+
+    /**
+     * Gets the fingerprint of the signed certificate of a package.
+     */
+    public static String getFingerprint(PackageInfo packageInfo, String packageName) throws
+            PackageManager.NameNotFoundException, IOException, NoSuchAlgorithmException,
+            CertificateException {
+        Signature[] signatures = packageInfo.signatures;
+        if (signatures.length != 1) {
+            throw new SecurityException(packageName + " has " + signatures.length + " signatures");
+        }
+        byte[] cert = signatures[0].toByteArray();
+        try (InputStream input = new ByteArrayInputStream(cert)) {
+            CertificateFactory factory = CertificateFactory.getInstance("X509");
+            X509Certificate x509 = (X509Certificate) factory.generateCertificate(input);
+            MessageDigest md = MessageDigest.getInstance("SHA256");
+            byte[] publicKey = md.digest(x509.getEncoded());
+            return toHexFormat(publicKey);
+        }
+    }
+
+    private static String toHexFormat(byte[] bytes) {
+        StringBuilder builder = new StringBuilder(bytes.length * 2);
+        for (int i = 0; i < bytes.length; i++) {
+            String hex = Integer.toHexString(bytes[i]);
+            int length = hex.length();
+            if (length == 1) {
+                hex = "0" + hex;
+            }
+            if (length > 2) {
+                hex = hex.substring(length - 2, length);
+            }
+            builder.append(hex.toUpperCase());
+            if (i < (bytes.length - 1)) {
+                builder.append(':');
+            }
+        }
+        return builder.toString();
+    }
+}
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/Util.java b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/Util.java
new file mode 100644
index 0000000..a86deea
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/Util.java
@@ -0,0 +1,312 @@
+/*
+ * 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 com.example.android.autofill.service.util;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.app.assist.AssistStructure.WindowNode;
+import android.os.Bundle;
+import android.service.autofill.FillContext;
+import android.service.autofill.SaveInfo;
+import android.support.annotation.NonNull;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewStructure.HtmlInfo;
+import android.view.autofill.AutofillValue;
+
+import com.google.common.base.Joiner;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+public final class Util {
+
+    public static final String EXTRA_DATASET_NAME = "dataset_name";
+    public static final String EXTRA_FOR_RESPONSE = "for_response";
+    public static final NodeFilter AUTOFILL_ID_FILTER = (node, id) ->
+            id.equals(node.getAutofillId());
+    private static final String TAG = "AutofillSample";
+    public static LogLevel sLoggingLevel = LogLevel.Off;
+
+    private static void bundleToString(StringBuilder builder, Bundle data) {
+        final Set<String> keySet = data.keySet();
+        builder.append("[Bundle with ").append(keySet.size()).append(" keys:");
+        for (String key : keySet) {
+            builder.append(' ').append(key).append('=');
+            Object value = data.get(key);
+            if ((value instanceof Bundle)) {
+                bundleToString(builder, (Bundle) value);
+            } else {
+                builder.append((value instanceof Object[])
+                        ? Arrays.toString((Object[]) value) : value);
+            }
+        }
+        builder.append(']');
+    }
+
+    public static String bundleToString(Bundle data) {
+        if (data == null) {
+            return "N/A";
+        }
+        final StringBuilder builder = new StringBuilder();
+        bundleToString(builder, data);
+        return builder.toString();
+    }
+
+    public static String getTypeAsString(int type) {
+        switch (type) {
+            case View.AUTOFILL_TYPE_TEXT:
+                return "TYPE_TEXT";
+            case View.AUTOFILL_TYPE_LIST:
+                return "TYPE_LIST";
+            case View.AUTOFILL_TYPE_NONE:
+                return "TYPE_NONE";
+            case View.AUTOFILL_TYPE_TOGGLE:
+                return "TYPE_TOGGLE";
+            case View.AUTOFILL_TYPE_DATE:
+                return "TYPE_DATE";
+        }
+        return "UNKNOWN_TYPE";
+    }
+
+    private static String getAutofillValueAndTypeAsString(AutofillValue value) {
+        if (value == null) return "null";
+
+        StringBuilder builder = new StringBuilder(value.toString()).append('(');
+        if (value.isText()) {
+            builder.append("isText");
+        } else if (value.isDate()) {
+            builder.append("isDate");
+        } else if (value.isToggle()) {
+            builder.append("isToggle");
+        } else if (value.isList()) {
+            builder.append("isList");
+        }
+        return builder.append(')').toString();
+    }
+
+    public static void dumpStructure(AssistStructure structure) {
+        if (logVerboseEnabled()) {
+            int nodeCount = structure.getWindowNodeCount();
+            logv("dumpStructure(): component=%s numberNodes=%d",
+                    structure.getActivityComponent(), nodeCount);
+            for (int i = 0; i < nodeCount; i++) {
+                logv("node #%d", i);
+                WindowNode node = structure.getWindowNodeAt(i);
+                dumpNode(new StringBuilder(), "  ", node.getRootViewNode(), 0);
+            }
+        }
+    }
+
+    private static void dumpNode(StringBuilder builder, String prefix, ViewNode node, int childNumber) {
+        builder.append(prefix)
+                .append("child #").append(childNumber).append("\n");
+
+        builder.append(prefix)
+                .append("autoFillId: ").append(node.getAutofillId())
+                .append("\tidEntry: ").append(node.getIdEntry())
+                .append("\tid: ").append(node.getId())
+                .append("\tclassName: ").append(node.getClassName())
+                .append('\n');
+
+        builder.append(prefix)
+                .append("focused: ").append(node.isFocused())
+                .append("\tvisibility").append(node.getVisibility())
+                .append("\tchecked: ").append(node.isChecked())
+                .append("\twebDomain: ").append(node.getWebDomain())
+                .append("\thint: ").append(node.getHint())
+                .append('\n');
+
+        HtmlInfo htmlInfo = node.getHtmlInfo();
+
+        if (htmlInfo != null) {
+            builder.append(prefix)
+                    .append("HTML TAG: ").append(htmlInfo.getTag())
+                    .append(" attrs: ").append(htmlInfo.getAttributes())
+                    .append('\n');
+        }
+
+        String[] afHints = node.getAutofillHints();
+        CharSequence[] options = node.getAutofillOptions();
+        builder.append(prefix).append("afType: ").append(getTypeAsString(node.getAutofillType()))
+                .append("\tafValue:")
+                .append(getAutofillValueAndTypeAsString(node.getAutofillValue()))
+                .append("\tafOptions:").append(options == null ? "N/A" : Arrays.toString(options))
+                .append("\tafHints: ").append(afHints == null ? "N/A" : Arrays.toString(afHints))
+                .append("\tinputType:").append(node.getInputType())
+                .append('\n');
+
+        int numberChildren = node.getChildCount();
+        builder.append(prefix).append("# children: ").append(numberChildren)
+                .append("\ttext: ").append(node.getText())
+                .append('\n');
+
+        final String prefix2 = prefix + "  ";
+        for (int i = 0; i < numberChildren; i++) {
+            dumpNode(builder, prefix2, node.getChildAt(i), i);
+        }
+        logv(builder.toString());
+    }
+
+    public static String getSaveTypeAsString(int type) {
+        List<String> types = new ArrayList<>();
+        if ((type & SaveInfo.SAVE_DATA_TYPE_ADDRESS) != 0) {
+            types.add("ADDRESS");
+        }
+        if ((type & SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD) != 0) {
+            types.add("CREDIT_CARD");
+        }
+        if ((type & SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS) != 0) {
+            types.add("EMAIL_ADDRESS");
+        }
+        if ((type & SaveInfo.SAVE_DATA_TYPE_USERNAME) != 0) {
+            types.add("USERNAME");
+        }
+        if ((type & SaveInfo.SAVE_DATA_TYPE_PASSWORD) != 0) {
+            types.add("PASSWORD");
+        }
+        if (types.isEmpty()) {
+            return "UNKNOWN(" + type + ")";
+        }
+        return Joiner.on('|').join(types);
+    }
+
+    /**
+     * Gets a node if it matches the filter criteria for the given id.
+     */
+    public static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id,
+            @NonNull NodeFilter filter) {
+        for (FillContext context : contexts) {
+            ViewNode node = findNodeByFilter(context.getStructure(), id, filter);
+            if (node != null) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets a node if it matches the filter criteria for the given id.
+     */
+    public static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id,
+            @NonNull NodeFilter filter) {
+        logv("Parsing request for activity %s", structure.getActivityComponent());
+        final int nodes = structure.getWindowNodeCount();
+        for (int i = 0; i < nodes; i++) {
+            final WindowNode windowNode = structure.getWindowNodeAt(i);
+            final ViewNode rootNode = windowNode.getRootViewNode();
+            final ViewNode node = findNodeByFilter(rootNode, id, filter);
+            if (node != null) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets a node if it matches the filter criteria for the given id.
+     */
+    public static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id,
+            @NonNull NodeFilter filter) {
+        if (filter.matches(node, id)) {
+            return node;
+        }
+        final int childrenSize = node.getChildCount();
+        if (childrenSize > 0) {
+            for (int i = 0; i < childrenSize; i++) {
+                final ViewNode found = findNodeByFilter(node.getChildAt(i), id, filter);
+                if (found != null) {
+                    return found;
+                }
+            }
+        }
+        return null;
+    }
+
+    public static void logd(String message, Object... params) {
+        if (logDebugEnabled()) {
+            Log.d(TAG, String.format(message, params));
+        }
+    }
+
+    public static void logv(String message, Object... params) {
+        if (logVerboseEnabled()) {
+            Log.v(TAG, String.format(message, params));
+        }
+    }
+
+    public static boolean logDebugEnabled() {
+        return sLoggingLevel.ordinal() >= LogLevel.Debug.ordinal();
+    }
+
+    public static boolean logVerboseEnabled() {
+        return sLoggingLevel.ordinal() >= LogLevel.Verbose.ordinal();
+    }
+
+    public static void logw(String message, Object... params) {
+        Log.w(TAG, String.format(message, params));
+    }
+
+    public static void logw(Throwable throwable, String message, Object... params) {
+        Log.w(TAG, String.format(message, params), throwable);
+    }
+
+    public static void loge(String message, Object... params) {
+        Log.e(TAG, String.format(message, params));
+    }
+
+    public static void loge(Throwable throwable, String message, Object... params) {
+        Log.e(TAG, String.format(message, params), throwable);
+    }
+
+    public static void setLoggingLevel(LogLevel level) {
+        sLoggingLevel = level;
+    }
+
+    /**
+     * Helper method for getting the index of a CharSequence object in an array.
+     */
+    public static int indexOf(@NonNull CharSequence[] array, CharSequence charSequence) {
+        int index = -1;
+        if (charSequence == null) {
+            return index;
+        }
+        for (int i = 0; i < array.length; i++) {
+            if (charSequence.equals(array[i])) {
+                index = i;
+                break;
+            }
+        }
+        return index;
+    }
+
+    public enum LogLevel {Off, Debug, Verbose}
+
+    public enum DalCheckRequirement {Disabled, LoginOnly, AllUrls}
+
+    /**
+     * Helper interface used to filter Assist nodes.
+     */
+    public interface NodeFilter {
+        /**
+         * Returns whether the node passes the filter for such given id.
+         */
+        boolean matches(ViewNode node, Object id);
+    }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/res/drawable/list_divider.xml b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/drawable/list_divider.xml
new file mode 100644
index 0000000..3d6d162
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/drawable/list_divider.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (c) 2017 Google Inc.
+  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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <size android:height="1dp" android:width="1dp" />
+    <solid android:color="@color/light_grey" />
+</shape>
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/activity_field_picker.xml b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/activity_field_picker.xml
new file mode 100644
index 0000000..949dbfb
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/activity_field_picker.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+-->
+<android.support.constraint.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_margin="@dimen/activity_vertical_margin">
+
+    <TextView
+        android:id="@+id/listTitle"
+        style="@style/Manual.Header"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        tools:text="@string/manual_data_picker_title"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/fieldsList"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginTop="@dimen/spacing_large"
+        android:background="@null"
+        android:elevation="@dimen/card_elevation"
+        android:listDivider="@drawable/list_divider"
+        android:orientation="vertical"
+        android:scrollbarStyle="insideOverlay"
+        android:scrollbars="vertical"
+        app:layoutManager="LinearLayoutManager"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        tools:listitem="@layout/dataset_field" />
+</android.support.constraint.ConstraintLayout>
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/dataset_field.xml b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/dataset_field.xml
new file mode 100644
index 0000000..b441dba
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/dataset_field.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+-->
+<android.support.constraint.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="@dimen/spacing_large"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <TextView
+        style="@style/Manual.Content"
+        android:id="@+id/fieldType"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/manual_dataset_name_label"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="Username" />
+
+</android.support.constraint.ConstraintLayout>
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/dataset_suggestion.xml b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/dataset_suggestion.xml
new file mode 100644
index 0000000..030de0a
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/dataset_suggestion.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+-->
+<android.support.constraint.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toBottomOf="@+id/fieldTypes"
+        android:src="@drawable/ic_person_black_24dp"/>
+
+    <TextView
+        style="@style/Manual.Header"
+        android:id="@+id/datasetNameLabel"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/manual_dataset_name_label"
+        app:layout_constraintStart_toEndOf="@+id/icon"
+        app:layout_constraintTop_toTopOf="parent" />
+    <TextView
+        style="@style/Manual.Content"
+        android:id="@+id/datasetName"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintStart_toStartOf="@+id/datasetNameLabel"
+        app:layout_constraintTop_toBottomOf="@+id/datasetNameLabel"
+        tools:text="dataset-1"/>
+    <TextView
+        style="@style/Manual.Header"
+        android:id="@+id/fieldTypesLabel"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/manual_field_types_label"
+        app:layout_constraintStart_toStartOf="@+id/datasetName"
+        app:layout_constraintTop_toBottomOf="@+id/datasetName"/>
+    <TextView
+        style="@style/Manual.Content"
+        android:id="@+id/fieldTypes"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintStart_toStartOf="@+id/fieldTypesLabel"
+        app:layout_constraintTop_toBottomOf="@+id/fieldTypesLabel"
+        tools:text="Username, password, and more."/>
+
+</android.support.constraint.ConstraintLayout>
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/multidataset_service_manual_activity.xml b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/multidataset_service_manual_activity.xml
new file mode 100644
index 0000000..56117aa
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/multidataset_service_manual_activity.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+-->
+<android.support.constraint.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_margin="@dimen/activity_vertical_margin"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/suggestionsList"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:background="@null"
+        android:elevation="@dimen/card_elevation"
+        android:listDivider="@drawable/list_divider"
+        android:orientation="vertical"
+        android:scrollbarStyle="insideOverlay"
+        android:scrollbars="vertical"
+        app:layoutManager="LinearLayoutManager"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior"
+        tools:listitem="@layout/dataset_suggestion" />
+</android.support.constraint.ConstraintLayout>
\ No newline at end of file
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/simple_service_auth_activity.xml b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/simple_service_auth_activity.xml
new file mode 100644
index 0000000..54fb675
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/layout/simple_service_auth_activity.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:importantForAutofill="noExcludeDescendants"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:orientation="vertical" >
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Authenticate?" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+        <Button
+            android:id="@+id/no"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="No" />
+        <Button
+            android:id="@+id/yes"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Yes" />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/res/raw/default_field_types b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/raw/default_field_types
new file mode 100644
index 0000000..a0e6b26
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/raw/default_field_types
@@ -0,0 +1,1177 @@
+[
+  {
+    "autofillHints": [
+      "country"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "countryseed"
+      },
+      "partition": 1,
+      "saveInfo": 2,
+      "typeName": "country"
+    }
+  },
+  {
+    "autofillHints": [
+      "transaction-amount"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "seed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "transaction-amount"
+    }
+  },
+  {
+    "autofillHints": [
+      "cc-family-name"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "familynameseed"
+      },
+      "partition": 3,
+      "saveInfo": 4,
+      "typeName": "cc-family-name"
+    }
+  },
+  {
+    "autofillHints": [
+      "postal-code",
+      "postalCode"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "1000seed"
+      },
+      "partition": 1,
+      "saveInfo": 2,
+      "typeName": "postalCode"
+    }
+  },
+  {
+    "autofillHints": [
+      "language"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [
+          "Bulgarian",
+          "Croatian",
+          "Czech",
+          "Danish",
+          "Dutch",
+          "English",
+          "Estonian"
+        ]
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "language"
+    }
+  },
+  {
+    "autofillHints": [
+      "tel-national"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "tel-nationalseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "tel-national"
+    }
+  },
+  {
+    "autofillHints": [
+      "transaction-currency"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [
+          "USD",
+          "CAD",
+          "KYD",
+          "CRC"
+        ]
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "transaction-currency"
+    }
+  },
+  {
+    "autofillHints": [
+      "billing"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "billingseed"
+      },
+      "partition": 1,
+      "saveInfo": 0,
+      "typeName": "billing"
+    }
+  },
+  {
+    "autofillHints": [
+      "emailAddress",
+      "email"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "emailAddressseed"
+      },
+      "partition": 2,
+      "saveInfo": 16,
+      "typeName": "emailAddress"
+    }
+  },
+  {
+    "autofillHints": [
+      "password",
+      "current-password"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "usernameseed"
+      },
+      "partition": 0,
+      "saveInfo": 1,
+      "typeName": "password"
+    }
+  },
+  {
+    "autofillHints": [
+      "honorific-suffix"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [
+          "san",
+          "kun",
+          "chan",
+          "sama"
+        ]
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "honorific-suffix"
+    }
+  },
+  {
+    "autofillHints": [
+      "shipping"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "shippingseed"
+      },
+      "partition": 1,
+      "saveInfo": 0,
+      "typeName": "shipping"
+    }
+  },
+  {
+    "autofillHints": [
+      "pager"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "pagerseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "pager"
+    }
+  },
+  {
+    "autofillHints": [
+      "impp"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "imppseed"
+      },
+      "partition": 2,
+      "saveInfo": 16,
+      "typeName": "impp"
+    }
+  },
+  {
+    "autofillHints": [
+      "bday-month"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "dateTemplate": "curr_time",
+        "strictExampleSet": [
+          "1",
+          "2",
+          "3",
+          "4",
+          "5",
+          "6",
+          "7",
+          "8",
+          "9",
+          "10",
+          "11",
+          "12"
+        ],
+        "textTemplate": "bday-monthseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "bday-month"
+    }
+  },
+  {
+    "autofillHints": [
+      "tel-local-prefix"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "tel-local-prefixseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "tel-local-prefix"
+    }
+  },
+  {
+    "autofillHints": [
+      "tel"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "telseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "tel"
+    }
+  },
+  {
+    "autofillHints": [
+      "fax"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "faxseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "fax"
+    }
+  },
+  {
+    "autofillHints": [
+      "honorific-prefix"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [
+          "Miss",
+          "Ms.",
+          "Mr.",
+          "Mx.",
+          "Sr.",
+          "Dr.",
+          "Lady",
+          "Lord"
+        ]
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "honorific-prefix"
+    }
+  },
+  {
+    "autofillHints": [
+      "bday-year"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [
+          "2000",
+          "1995",
+          "1990",
+          "1985",
+          "1980",
+          "1975",
+          "1970",
+          "1965",
+          "1960",
+          "1955",
+          "1950",
+          "1945"
+        ]
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "bday-year"
+    }
+  },
+  {
+    "autofillHints": [
+      "creditCardExpirationMonth",
+      "cc-exp-month"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3,
+        4
+      ],
+      "fakeData": {
+        "dateTemplate": "curr_time",
+        "strictExampleSet": [
+          "1",
+          "2",
+          "3",
+          "4",
+          "5",
+          "6",
+          "7",
+          "8",
+          "9",
+          "10",
+          "11",
+          "12"
+        ]
+      },
+      "partition": 3,
+      "saveInfo": 4,
+      "typeName": "creditCardExpirationMonth"
+    }
+  },
+  {
+    "autofillHints": [
+      "work"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "workseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "work"
+    }
+  },
+  {
+    "autofillHints": [
+      "cc-csc",
+      "creditCardSecurityCode"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "seedseedseed"
+      },
+      "partition": 3,
+      "saveInfo": 4,
+      "typeName": "creditCardSecurityCode"
+    }
+  },
+  {
+    "autofillHints": [
+      "cc-type"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "cc-typeseed"
+      },
+      "partition": 3,
+      "saveInfo": 4,
+      "typeName": "cc-type"
+    }
+  },
+  {
+    "autofillHints": [
+      "bday-day"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "dateTemplate": "curr_time",
+        "strictExampleSet": [],
+        "textTemplate": "seed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "bday-day"
+    }
+  },
+  {
+    "autofillHints": [
+      "address-line2"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "Bldg. seed"
+      },
+      "partition": 1,
+      "saveInfo": 2,
+      "typeName": "address-line2"
+    }
+  },
+  {
+    "autofillHints": [
+      "address-line1"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "seed Fake Ln."
+      },
+      "partition": 1,
+      "saveInfo": 2,
+      "typeName": "address-line1"
+    }
+  },
+  {
+    "autofillHints": [
+      "postalAddress"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "seed Fake Ln, Fake, FA, FAA 10001"
+      },
+      "partition": 1,
+      "saveInfo": 2,
+      "typeName": "postalAddress"
+    }
+  },
+  {
+    "autofillHints": [
+      "address-line3"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "Suite seed"
+      },
+      "partition": 1,
+      "saveInfo": 2,
+      "typeName": "address-line3"
+    }
+  },
+  {
+    "autofillHints": [
+      "phone"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "seed2345678910"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "phone"
+    }
+  },
+  {
+    "autofillHints": [
+      "tel_extension"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "tel_extensionseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "tel_extension"
+    }
+  },
+  {
+    "autofillHints": [
+      "creditCardNumber",
+      "cc-number"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "seed234567"
+      },
+      "partition": 3,
+      "saveInfo": 4,
+      "typeName": "creditCardNumber"
+    }
+  },
+  {
+    "autofillHints": [
+      "name"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "nameseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "name"
+    }
+  },
+  {
+    "autofillHints": [
+      "additional-name"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "additional-nameseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "additional-name"
+    }
+  },
+  {
+    "autofillHints": [
+      "tel-local"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "tel-localseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "tel-local"
+    }
+  },
+  {
+    "autofillHints": [
+      "bday"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        4
+      ],
+      "fakeData": {
+        "dateTemplate": "curr_time",
+        "strictExampleSet": []
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "bday"
+    }
+  },
+  {
+    "autofillHints": [
+      "street-address"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "street-addressseed"
+      },
+      "partition": 1,
+      "saveInfo": 2,
+      "typeName": "street-address"
+    }
+  },
+  {
+    "autofillHints": [
+      "cc-given-name"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "cc-given-nameseed"
+      },
+      "partition": 3,
+      "saveInfo": 4,
+      "typeName": "cc-given-name"
+    }
+  },
+  {
+    "autofillHints": [
+      "creditCardExpirationDate",
+      "cc-exp"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        4
+      ],
+      "fakeData": {
+        "dateTemplate": "curr_time",
+        "strictExampleSet": []
+      },
+      "partition": 3,
+      "saveInfo": 4,
+      "typeName": "creditCardExpirationDate"
+    }
+  },
+  {
+    "autofillHints": [
+      "creditCardExpirationYear",
+      "cc-exp-year"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3,
+        4
+      ],
+      "fakeData": {
+        "dateTemplate": "curr_time",
+        "strictExampleSet": [],
+        "textTemplate": "creditCardExpirationYearseed"
+      },
+      "partition": 3,
+      "saveInfo": 4,
+      "typeName": "creditCardExpirationYear"
+    }
+  },
+  {
+    "autofillHints": [
+      "organization-title"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "organization-titleseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "organization-title"
+    }
+  },
+  {
+    "autofillHints": [
+      "sex"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [
+          "Male",
+          "Female",
+          "Other"
+        ]
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "sex"
+    }
+  },
+  {
+    "autofillHints": [
+      "section-"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "section-seed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "section-"
+    }
+  },
+  {
+    "autofillHints": [
+      "country-name"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [
+          "USA",
+          "Mexico",
+          "Canada"
+        ]
+      },
+      "partition": 1,
+      "saveInfo": 2,
+      "typeName": "country-name"
+    }
+  },
+  {
+    "autofillHints": [
+      "photo"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "photoseed.jpg"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "photo"
+    }
+  },
+  {
+    "autofillHints": [
+      "tel-area-code"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "tel-area-codeseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "tel-area-code"
+    }
+  },
+  {
+    "autofillHints": [
+      "new-password"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "new-passwordseed"
+      },
+      "partition": 0,
+      "saveInfo": 1,
+      "typeName": "new-password"
+    }
+  },
+  {
+    "autofillHints": [
+      "tel-local-suffix"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "tel-local-suffixseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "tel-local-suffix"
+    }
+  },
+  {
+    "autofillHints": [
+      "family-name"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "family-nameseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "family-name"
+    }
+  },
+  {
+    "autofillHints": [
+      "url"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "https://www.google.com/?count\u003dseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "url"
+    }
+  },
+  {
+    "autofillHints": [
+      "home"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "homeseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "home"
+    }
+  },
+  {
+    "autofillHints": [
+      "address-level4"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "address-level4seed"
+      },
+      "partition": 1,
+      "saveInfo": 2,
+      "typeName": "address-level4"
+    }
+  },
+  {
+    "autofillHints": [
+      "address-level3"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "address-level3seed"
+      },
+      "partition": 1,
+      "saveInfo": 2,
+      "typeName": "address-level3"
+    }
+  },
+  {
+    "autofillHints": [
+      "organization"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "organizationseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "organization"
+    }
+  },
+  {
+    "autofillHints": [
+      "cc-additional-name"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "cc-additional-nameseed"
+      },
+      "partition": 3,
+      "saveInfo": 4,
+      "typeName": "cc-additional-name"
+    }
+  },
+  {
+    "autofillHints": [
+      "address-level2"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "address-level2seed"
+      },
+      "partition": 1,
+      "saveInfo": 2,
+      "typeName": "address-level2"
+    }
+  },
+  {
+    "autofillHints": [
+      "given-name"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "given-nameseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "given-name"
+    }
+  },
+  {
+    "autofillHints": [
+      "address-level1"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "address-level1seed"
+      },
+      "partition": 1,
+      "saveInfo": 2,
+      "typeName": "address-level1"
+    }
+  },
+  {
+    "autofillHints": [
+      "creditCardExpirationDay"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3,
+        4
+      ],
+      "fakeData": {
+        "dateTemplate": "curr_time",
+        "strictExampleSet": [],
+        "textTemplate": "creditCardExpirationDayseed"
+      },
+      "partition": 3,
+      "saveInfo": 4,
+      "typeName": "creditCardExpirationDay"
+    }
+  },
+  {
+    "autofillHints": [
+      "cc-name"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "Firstnameseed Lastnameseed"
+      },
+      "partition": 3,
+      "saveInfo": 4,
+      "typeName": "cc-name"
+    }
+  },
+  {
+    "autofillHints": [
+      "username"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "usernameseed"
+      },
+      "partition": 0,
+      "saveInfo": 8,
+      "typeName": "username"
+    }
+  },
+  {
+    "autofillHints": [
+      "tel-country-code"
+    ],
+    "fieldType": {
+      "autofillTypes": [
+        1,
+        3
+      ],
+      "fakeData": {
+        "strictExampleSet": [],
+        "textTemplate": "tel-country-codeseed"
+      },
+      "partition": 0,
+      "saveInfo": 0,
+      "typeName": "tel-country-code"
+    }
+  }
+]
diff --git a/prebuilts/gradle/AutofillFramework/afservice/src/main/res/xml/heuristics_service.xml b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/xml/heuristics_service.xml
new file mode 100644
index 0000000..fed1c47
--- /dev/null
+++ b/prebuilts/gradle/AutofillFramework/afservice/src/main/res/xml/heuristics_service.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+-->
+
+<!-- TODO(b/114236837): use its own Settings Activity -->
+<autofill-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:settingsActivity="com.example.android.autofill.service.settings.SettingsActivity">
+
+  <!-- sample app -->
+  <compatibility-package
+    android:name="com.example.android.autofill.app"
+    android:maxLongVersionCode="10000000000"/>
+
+  <!-- well-known browswers, alphabetical order -->
+  <compatibility-package
+    android:name="com.android.chrome"
+    android:maxLongVersionCode="10000000000"/>
+  <compatibility-package
+    android:name="com.chrome.beta"
+    android:maxLongVersionCode="10000000000"/>
+  <compatibility-package
+    android:name="com.chrome.dev"
+    android:maxLongVersionCode="10000000000"/>
+  <compatibility-package
+    android:name="com.chrome.canary"
+    android:maxLongVersionCode="10000000000"/>
+  <compatibility-package
+    android:name="com.microsoft.emmx"
+    android:maxLongVersionCode="10000000000"/>
+  <compatibility-package
+    android:name="com.opera.browser"
+    android:maxLongVersionCode="10000000000"/>
+  <compatibility-package
+    android:name="com.opera.browser.beta"
+    android:maxLongVersionCode="10000000000"/>
+  <compatibility-package
+    android:name="com.opera.mini.native"
+    android:maxLongVersionCode="10000000000"/>
+  <compatibility-package
+    android:name="com.opera.mini.native.beta"
+    android:maxLongVersionCode="10000000000"/>
+  <compatibility-package
+    android:name="com.sec.android.app.sbrowser"
+    android:maxLongVersionCode="10000000000"/>
+  <compatibility-package
+    android:name="com.sec.android.app.sbrowser.beta"
+    android:maxLongVersionCode="10000000000"/>
+  <compatibility-package
+    android:name="org.chromium.chrome"
+    android:maxLongVersionCode="10000000000"/>
+  <compatibility-package
+    android:name="org.mozilla.fennec_aurora"
+    android:maxLongVersionCode="10000000000"/>
+  <compatibility-package
+    android:name="org.mozilla.firefox"
+    android:maxLongVersionCode="10000000000"/>
+  <compatibility-package
+    android:name="org.mozilla.firefox_beta"
+    android:maxLongVersionCode="10000000000"/>
+</autofill-service>
diff --git a/prebuilts/gradle/EmojiCompat/README.md b/prebuilts/gradle/EmojiCompat/README.md
index e283cbc..b6e5a1b 100644
--- a/prebuilts/gradle/EmojiCompat/README.md
+++ b/prebuilts/gradle/EmojiCompat/README.md
@@ -96,7 +96,7 @@
 --------------
 
 - Android SDK 27
-- Android Build Tools v27.0.2
+- Android Build Tools v27.0.3
 - Android Support Repository
 
 Screenshots
diff --git a/prebuilts/gradle/EmojiCompat/app/src/main/AndroidManifest.xml b/prebuilts/gradle/EmojiCompat/app/src/main/AndroidManifest.xml
index 4f2453d..22076c1 100644
--- a/prebuilts/gradle/EmojiCompat/app/src/main/AndroidManifest.xml
+++ b/prebuilts/gradle/EmojiCompat/app/src/main/AndroidManifest.xml
@@ -19,8 +19,12 @@
     xmlns:tools="http://schemas.android.com/tools"
     package="com.example.android.emojicompat">
 
+    <!-- Adding this meta-data will download the font when application is installed from
+        Google Play Store. This way the font will be downloaded and ready when your app starts
+        for the first time. -->
+    <meta-data android:name="fontProviderRequests" android:value="Noto Color Emoji Compat"/>
+
     <application
-        android:name=".EmojiCompatApplication"
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
diff --git a/prebuilts/gradle/EmojiCompat/app/src/main/java/com/example/android/emojicompat/EmojiCompatApplication.java b/prebuilts/gradle/EmojiCompat/app/src/main/java/com/example/android/emojicompat/EmojiCompatApplication.java
deleted file mode 100644
index 81543ea..0000000
--- a/prebuilts/gradle/EmojiCompat/app/src/main/java/com/example/android/emojicompat/EmojiCompatApplication.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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 com.example.android.emojicompat;
-
-import android.app.Application;
-import android.support.annotation.Nullable;
-import android.support.text.emoji.EmojiCompat;
-import android.support.text.emoji.FontRequestEmojiCompatConfig;
-import android.support.text.emoji.bundled.BundledEmojiCompatConfig;
-import android.support.v4.provider.FontRequest;
-import android.util.Log;
-
-
-/**
- * This application uses EmojiCompat.
- */
-public class EmojiCompatApplication extends Application {
-
-    private static final String TAG = "EmojiCompatApplication";
-
-    /** Change this to {@code false} when you want to use the downloadable Emoji font. */
-    private static final boolean USE_BUNDLED_EMOJI = true;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-
-        final EmojiCompat.Config config;
-        if (USE_BUNDLED_EMOJI) {
-            // Use the bundled font for EmojiCompat
-            config = new BundledEmojiCompatConfig(getApplicationContext());
-        } else {
-            // Use a downloadable font for EmojiCompat
-            final FontRequest fontRequest = new FontRequest(
-                    "com.google.android.gms.fonts",
-                    "com.google.android.gms",
-                    "Noto Color Emoji Compat",
-                    R.array.com_google_android_gms_fonts_certs);
-            config = new FontRequestEmojiCompatConfig(getApplicationContext(), fontRequest)
-                    .setReplaceAll(true)
-                    .registerInitCallback(new EmojiCompat.InitCallback() {
-                        @Override
-                        public void onInitialized() {
-                            Log.i(TAG, "EmojiCompat initialized");
-                        }
-
-                        @Override
-                        public void onFailed(@Nullable Throwable throwable) {
-                            Log.e(TAG, "EmojiCompat initialization failed", throwable);
-                        }
-                    });
-        }
-        EmojiCompat.init(config);
-    }
-
-}
diff --git a/prebuilts/gradle/EmojiCompat/app/src/main/java/com/example/android/emojicompat/MainActivity.java b/prebuilts/gradle/EmojiCompat/app/src/main/java/com/example/android/emojicompat/MainActivity.java
index 2f93c54..4769271 100644
--- a/prebuilts/gradle/EmojiCompat/app/src/main/java/com/example/android/emojicompat/MainActivity.java
+++ b/prebuilts/gradle/EmojiCompat/app/src/main/java/com/example/android/emojicompat/MainActivity.java
@@ -18,8 +18,13 @@
 
 import android.content.Context;
 import android.os.Bundle;
+import android.support.annotation.Nullable;
 import android.support.text.emoji.EmojiCompat;
+import android.support.text.emoji.FontRequestEmojiCompatConfig;
+import android.support.text.emoji.bundled.BundledEmojiCompatConfig;
+import android.support.v4.provider.FontRequest;
 import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
 import android.widget.TextView;
 
 import java.lang.ref.WeakReference;
@@ -27,6 +32,11 @@
 
 public class MainActivity extends AppCompatActivity {
 
+    private static final String TAG = "MainActivity";
+
+    /** Change this to {@code false} when you want to use the downloadable Emoji font. */
+    private static final boolean USE_BUNDLED_EMOJI = true;
+
     // [U+1F469] (WOMAN) + [U+200D] (ZERO WIDTH JOINER) + [U+1F4BB] (PERSONAL COMPUTER)
     private static final String WOMAN_TECHNOLOGIST = "\uD83D\uDC69\u200D\uD83D\uDCBB";
 
@@ -38,6 +48,9 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        initEmojiCompat();
+
         setContentView(R.layout.activity_main);
 
         // TextView variant provided by EmojiCompat library
@@ -61,6 +74,37 @@
         customTextView.setText(getString(R.string.custom_text_view, EMOJI));
     }
 
+    private void initEmojiCompat() {
+        final EmojiCompat.Config config;
+        if (USE_BUNDLED_EMOJI) {
+            // Use the bundled font for EmojiCompat
+            config = new BundledEmojiCompatConfig(getApplicationContext());
+        } else {
+            // Use a downloadable font for EmojiCompat
+            final FontRequest fontRequest = new FontRequest(
+                    "com.google.android.gms.fonts",
+                    "com.google.android.gms",
+                    "Noto Color Emoji Compat",
+                    R.array.com_google_android_gms_fonts_certs);
+            config = new FontRequestEmojiCompatConfig(getApplicationContext(), fontRequest);
+        }
+
+        config.setReplaceAll(true)
+                .registerInitCallback(new EmojiCompat.InitCallback() {
+                    @Override
+                    public void onInitialized() {
+                        Log.i(TAG, "EmojiCompat initialized");
+                    }
+
+                    @Override
+                    public void onFailed(@Nullable Throwable throwable) {
+                        Log.e(TAG, "EmojiCompat initialization failed", throwable);
+                    }
+                });
+
+        EmojiCompat.init(config);
+    }
+
     private static class InitCallback extends EmojiCompat.InitCallback {
 
         private final WeakReference<TextView> mRegularTextViewRef;
diff --git a/prebuilts/gradle/EmojiCompat/build.gradle b/prebuilts/gradle/EmojiCompat/build.gradle
index 5d5882d..97c6ae3 100644
--- a/prebuilts/gradle/EmojiCompat/build.gradle
+++ b/prebuilts/gradle/EmojiCompat/build.gradle
@@ -16,6 +16,7 @@
 
 buildscript {
     repositories {
+        google()
         jcenter()
     }
     dependencies {
@@ -25,8 +26,8 @@
 
 allprojects {
     repositories {
-        jcenter()
         google()
+        jcenter()
     }
 }