Changes on Autofill Service project:

- Renamed BasicHeuristicsService to HeuristicsService
- Split HeuristicsService from BasicService
- Added compat-mode support to Chromium
- Supported authentication on HeuristicsService
- Changed value to "N-hint" to make it easier to filter out datasets
- Added settings to HeuristicService
- Made BasicService final, as it's kind of the "smallest" service possible

Bug: 114236837
Test: ./gradlew :afservice:installDebug

Change-Id: Ie36a8009d5ef39b9f82117f47a86ba11a9a4a9c1
diff --git a/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml b/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
index 6ece581..8c0b775 100644
--- a/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
+++ b/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
@@ -36,12 +36,12 @@
         </service>
 
         <service
-            android:name=".simple.BasicHeuristicsService"
-            android:label="Basic Heuristics 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/basic_heuristics_service"/>
+                android:resource="@xml/heuristics_service"/>
             <intent-filter>
                 <action android:name="android.service.autofill.AutofillService" />
             </intent-filter>
@@ -53,6 +53,11 @@
             android:label="@string/authentication_name" />
 
         <activity
+            android:name=".simple.SimpleAuthActivity"
+            android:taskAffinity=".simple.SimpleAuthActivity"
+            android:label="@string/authentication_name" />
+
+        <activity
             android:name=".ManualActivity"
             android:taskAffinity=".ManualActivity"
             android:label="@string/manual_name" />
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java
deleted file mode 100644
index 81aac2b..0000000
--- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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.ViewNode;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-
-import com.example.android.autofill.service.MyAutofillService;
-
-/**
- * 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 BasicHeuristicsService extends BasicService {
-
-    private static final String TAG = "BasicHeuristicsService";
-
-    @Override
-    @Nullable
-    protected String getHint(@NonNull ViewNode node) {
-
-        // First try the explicit autofill hints...
-
-        String hint = super.getHint(node);
-        if (hint != null) return hint;
-
-        // Then try some rudimentary heuristics based on other node properties
-
-        String viewHint = node.getHint();
-        hint = inferHint(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(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(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(@Nullable String string) {
-        if (string == null) return null;
-
-        string = string.toLowerCase();
-        if (string.contains("label")) {
-            Log.v(TAG, "Ignroing '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;
-
-        return null;
-    }
-}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java
index 73210be..16937e7 100644
--- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java
@@ -53,7 +53,7 @@
  * it lacks fundamental security requirements such as data partitioning and package verification
  * &mdashthese requirements are fullfilled by {@link MyAutofillService}.
  */
-public class BasicService extends AutofillService {
+public final class BasicService extends AutofillService {
 
     private static final String TAG = "BasicService";
 
@@ -88,9 +88,9 @@
             for (Entry<String, AutofillId> field : fields.entrySet()) {
                 String hint = field.getKey();
                 AutofillId id = field.getValue();
-                String value = hint + i;
-                // We're simple - our dataset values are hardcoded as "hintN" (for example,
-                // "username1", "username2") and they're displayed as such, except if they're a
+                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);
@@ -141,15 +141,19 @@
      */
     private void addAutofillableFields(@NonNull Map<String, AutofillId> fields,
             @NonNull ViewNode node) {
-        int type = node.getAutofillType();
-        String hint = getHint(node);
+        String[] hints = node.getAutofillHints();
+        if (hints == null) return;
+
+        // 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);
+                Log.v(TAG, "Setting hint '" + hint + "' on " + id);
                 fields.put(hint, id);
             } else {
-                Log.v(TAG, "Ignoring hint " + hint + " on " + id
+                Log.v(TAG, "Ignoring hint '" + hint + "' on " + id
                         + " because it was already set");
             }
         }
@@ -160,29 +164,11 @@
     }
 
     /**
-     * Gets the autofill hint associated with the given node.
-     *
-     * <p>By default it just return the first entry on the node's
-     * {@link ViewNode#getAutofillHints() autofillHints} (when available), but subclasses could
-     * extend it to use heuristics when the app developer didn't explicitly provide these hints.
-     *
-     */
-    @Nullable
-    protected String getHint(@NonNull ViewNode node) {
-        String[] hints = node.getAutofillHints();
-        if (hints == null) return null;
-
-        // We're simple, we only care about the first hint
-        String hint = hints[0].toLowerCase();
-        return hint;
-    }
-
-    /**
      * Helper method to get the {@link AssistStructure} associated with the latest request
      * in an autofill context.
      */
     @NonNull
-    private static AssistStructure getLatestAssistStructure(@NonNull FillRequest request) {
+    static AssistStructure getLatestAssistStructure(@NonNull FillRequest request) {
         List<FillContext> fillContexts = request.getFillContexts();
         return fillContexts.get(fillContexts.size() - 1).getStructure();
     }
@@ -191,7 +177,7 @@
      * Helper method to create a dataset presentation with the given text.
      */
     @NonNull
-    private static RemoteViews newDatasetPresentation(@NonNull String packageName,
+    static RemoteViews newDatasetPresentation(@NonNull String packageName,
             @NonNull CharSequence text) {
         RemoteViews presentation =
                 new RemoteViews(packageName, R.layout.multidataset_service_list_item);
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java
new file mode 100644
index 0000000..e19288e
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java
@@ -0,0 +1,309 @@
+/*
+ * 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(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(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(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(@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;
+
+        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/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/SimpleAuthActivity.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/SimpleAuthActivity.java
new file mode 100644
index 0000000..4ff97a7
--- /dev/null
+++ b/input/autofill/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/input/autofill/AutofillFramework/afservice/src/main/res/layout/simple_service_auth_activity.xml b/input/autofill/AutofillFramework/afservice/src/main/res/layout/simple_service_auth_activity.xml
new file mode 100644
index 0000000..54fb675
--- /dev/null
+++ b/input/autofill/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/input/autofill/AutofillFramework/afservice/src/main/res/xml/basic_heuristics_service.xml b/input/autofill/AutofillFramework/afservice/src/main/res/xml/heuristics_service.xml
similarity index 88%
rename from input/autofill/AutofillFramework/afservice/src/main/res/xml/basic_heuristics_service.xml
rename to input/autofill/AutofillFramework/afservice/src/main/res/xml/heuristics_service.xml
index cfaabb0..fed1c47 100644
--- a/input/autofill/AutofillFramework/afservice/src/main/res/xml/basic_heuristics_service.xml
+++ b/input/autofill/AutofillFramework/afservice/src/main/res/xml/heuristics_service.xml
@@ -14,13 +14,16 @@
  * limitations under the License.
 -->
 
-<autofill-service xmlns:android="http://schemas.android.com/apk/res/android">
+<!-- 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 -->
+  <!-- well-known browswers, alphabetical order -->
   <compatibility-package
     android:name="com.android.chrome"
     android:maxLongVersionCode="10000000000"/>
@@ -55,6 +58,9 @@
     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