Add test cases for hints config

Bug: 219844915
Test: atest android.autofillservice.cts.dialog.LoginActivityTest
Change-Id: Ia6aacd933f0f3d7ee5f49e7fa98223e390b219a6
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index 28d4ebd..bbce105 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -145,6 +145,7 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".activities.FieldsNoPasswordActivity"/>
         <activity android:name=".activities.AugmentedAuthActivity" />
         <activity android:name=".activities.SimpleAfterLoginActivity"/>
         <activity android:name=".activities.SimpleBeforeLoginActivity"/>
diff --git a/tests/autofillservice/res/layout/multiple_hints_without_password_activity.xml b/tests/autofillservice/res/layout/multiple_hints_without_password_activity.xml
new file mode 100644
index 0000000..620c6e7
--- /dev/null
+++ b/tests/autofillservice/res/layout/multiple_hints_without_password_activity.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 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="wrap_content"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:orientation="vertical" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/username_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Username" />
+
+        <EditText
+            android:id="@+id/username"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:autofillHints="username" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/cc_number_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="CC Number" />
+
+        <EditText
+            android:id="@+id/cc_number"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:autofillHints="creditCardNumber" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/email_address_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Email Address" />
+
+        <EditText
+            android:id="@+id/email_address"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:autofillHints="emailAddress" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/phone_number_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Phone Number" />
+
+        <EditText
+            android:id="@+id/phone_number"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:autofillHints="phone" />
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/FieldsNoPasswordActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/FieldsNoPasswordActivity.java
new file mode 100644
index 0000000..fad2808
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/FieldsNoPasswordActivity.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.view.WindowInsets;
+import android.widget.EditText;
+
+
+/**
+ * The Activity that is the same as {@link LoginActivity} layout but without password field.
+ */
+public class FieldsNoPasswordActivity extends AbstractAutoFillActivity {
+    public static final String STRING_ID_PHONE = "phone_number";
+    public static final String STRING_ID_CREDIT_CARD_NUMBER = "cc_number";
+    public static final String STRING_ID_EMAILADDRESS = "email_address";
+
+    private static FieldsNoPasswordActivity sCurrentActivity;
+
+    private EditText mUsernameEditText;
+
+    public static FieldsNoPasswordActivity getCurrentActivity() {
+        return sCurrentActivity;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(getContentView());
+        sCurrentActivity = this;
+
+        mUsernameEditText = findViewById(R.id.username);
+    }
+
+    protected int getContentView() {
+        return R.layout.multiple_hints_without_password_activity;
+    }
+
+    public WindowInsets getRootWindowInsets() {
+        return mUsernameEditText.getRootWindowInsets();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
index dca70f8..347ded0 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
@@ -16,6 +16,7 @@
 
 package android.autofillservice.cts.commontests;
 
+import static android.autofillservice.cts.testcore.Helper.DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS;
 import static android.autofillservice.cts.testcore.Helper.getContext;
 import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.SERVICE_NAME;
 import static android.content.Context.CLIPBOARD_SERVICE;
@@ -298,6 +299,11 @@
                         AutofillManager.DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED,
                         Boolean.toString(false)))
                 //
+                // Hints list of Fill Dialog should be empty by default
+                .around(new DeviceConfigStateChangerRule(sContext, DeviceConfig.NAMESPACE_AUTOFILL,
+                        DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS,
+                        ""))
+                //
                 // Finally, let subclasses add their own rules (like ActivityTestRule)
                 .around(getMainTestRule());
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dialog/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dialog/LoginActivityTest.java
index ec8a4d1..6807600 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/dialog/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/dialog/LoginActivityTest.java
@@ -16,21 +16,28 @@
 
 package android.autofillservice.cts.dialog;
 
+import static android.autofillservice.cts.activities.FieldsNoPasswordActivity.STRING_ID_CREDIT_CARD_NUMBER;
+import static android.autofillservice.cts.activities.FieldsNoPasswordActivity.STRING_ID_EMAILADDRESS;
+import static android.autofillservice.cts.activities.FieldsNoPasswordActivity.STRING_ID_PHONE;
 import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
 import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
 import static android.autofillservice.cts.testcore.Helper.assertHasFlags;
+import static android.autofillservice.cts.testcore.Helper.assertNoFlags;
 import static android.autofillservice.cts.testcore.Helper.enableFillDialogFeature;
 import static android.autofillservice.cts.testcore.Helper.isImeShowing;
+import static android.autofillservice.cts.testcore.Helper.setFillDialogHints;
 import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
 
-
 import static com.google.common.truth.Truth.assertThat;
 
+import android.autofillservice.cts.activities.FieldsNoPasswordActivity;
 import android.autofillservice.cts.activities.LoginActivity;
 import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
 import android.autofillservice.cts.testcore.CannedFillResponse;
 import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
 import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.content.Intent;
 import android.support.test.uiautomator.UiObject2;
 import android.view.View;
 
@@ -330,4 +337,135 @@
 
         activity.assertAutoFilled();
     }
+
+    @Test
+    public void testHints_empty_notShowFillDialog() throws Exception {
+        testHintsNotMatch("");
+    }
+
+    @Test
+    public void testHints_notExisting_notShowFillDialog() throws Exception {
+        testHintsNotMatch("name:postalAddress:postalCode");
+    }
+
+    private void testHintsNotMatch(String hints) throws Exception {
+        // Set hints config, enable fill dialog and test service
+        setFillDialogHints(sContext, hints);
+        enableService();
+
+        // Start activity and autofill is not triggered
+        final FieldsNoPasswordActivity activity = startNoPasswordActivity();
+        mUiBot.waitForIdleSync();
+
+        sReplier.assertNoUnhandledFillRequests();
+        mUiBot.waitForIdleSync();
+
+        // Set response with a dataset
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(STRING_ID_PHONE, "0123456789")
+                        .setField(STRING_ID_CREDIT_CARD_NUMBER, "1234567890")
+                        .setField(STRING_ID_EMAILADDRESS, "dude@test")
+                        .setPresentation(createPresentation("Dropdown Presentation"))
+                        .setDialogPresentation(createPresentation("Dialog Presentation"))
+                        .build())
+                .setDialogHeader(createPresentation("Dialog Header"))
+                .setDialogTriggerIds(ID_USERNAME);
+        sReplier.addResponse(builder.build());
+
+        // Click on username field to trigger fill dialog
+        mUiBot.selectByRelativeIdFromUiDevice(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        // Check onFillRequest is called now, and the fill dialog is not shown
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertNoFlags(fillRequest.flags, FLAG_SUPPORTS_FILL_DIALOG);
+        mUiBot.assertNoFillDialog();
+
+        // Verify IME is not shown
+        assertThat(isImeShowing(activity.getRootWindowInsets())).isTrue();
+
+        // Verify dropdown UI is shown and works
+        mUiBot.selectDataset("Dropdown Presentation");
+    }
+
+    @Test
+    public void testHints_emptyButEnabled_showFillDialog() throws Exception {
+        testHintsNotMatchButFeatureEnabled("");
+    }
+
+    @Test
+    public void testHints_notExistingButEnabled_showFillDialog() throws Exception {
+        testHintsNotMatchButFeatureEnabled("name:postalAddress:postalCode");
+    }
+
+    // Tests the activity does not have allowed hints but fill dialog is enabled
+    private void testHintsNotMatchButFeatureEnabled(String hints) throws Exception {
+        // Enable fill dialog feature
+        enableFillDialogFeature(sContext);
+        // The test step is the same as if there is at least one match in the allowed
+        // list when the feature is enabled.
+        testHintsConfigMatchAtLeastOneField(hints);
+    }
+
+    @Test
+    public void testHints_username_showFillDialog() throws Exception {
+        testHintsConfigMatchAtLeastOneField("username");
+    }
+
+    @Test
+    public void testHints_emailAddress_showFillDialog() throws Exception {
+        testHintsConfigMatchAtLeastOneField("emailAddress");
+    }
+
+    @Test
+    public void testHints_usernameAndPhone_showFillDialog() throws Exception {
+        testHintsConfigMatchAtLeastOneField("username:phone");
+    }
+
+    private void testHintsConfigMatchAtLeastOneField(String hints) throws Exception {
+        // Set hints config, enable fill dialog and test service
+        setFillDialogHints(sContext, hints);
+        enableService();
+
+        // Set response with a dataset
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(STRING_ID_PHONE, "0123456789")
+                        .setField(STRING_ID_CREDIT_CARD_NUMBER, "1234567890")
+                        .setField(STRING_ID_EMAILADDRESS, "dude@test")
+                        .setPresentation(createPresentation("Dropdown Presentation"))
+                        .setDialogPresentation(createPresentation("Dialog Presentation"))
+                        .build())
+                .setDialogHeader(createPresentation("Dialog Header"))
+                .setDialogTriggerIds(ID_USERNAME);
+        sReplier.addResponse(builder.build());
+
+        // Start activity and autofill
+        final FieldsNoPasswordActivity activity = startNoPasswordActivity();
+        mUiBot.waitForIdleSync();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_SUPPORTS_FILL_DIALOG);
+
+        // Click on password field to trigger fill dialog
+        mUiBot.selectByRelativeIdFromUiDevice(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        // Verify IME is not shown
+        assertThat(isImeShowing(activity.getRootWindowInsets())).isFalse();
+
+        // Verify fill dialog is shown and works
+        mUiBot.selectFillDialogDataset("Dialog Presentation");
+    }
+
+    private FieldsNoPasswordActivity startNoPasswordActivity() throws Exception {
+        final Intent intent = new Intent(mContext, FieldsNoPasswordActivity.class)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+        mUiBot.assertShownByRelativeId(ID_USERNAME_LABEL);
+        return FieldsNoPasswordActivity.getCurrentActivity();
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
index 20d9370..672cfb1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
@@ -143,6 +143,8 @@
             "SETTINGS_SHELL_CMD_TIMEOUT", OneTimeSettingsListener.DEFAULT_TIMEOUT_MS / 2, 2,
             OneTimeSettingsListener.DEFAULT_TIMEOUT_MS);
 
+    public static final String DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS = "autofill_dialog_hints";
+
     /**
      * Helper interface used to filter nodes.
      *
@@ -1340,6 +1342,11 @@
                 .that(actualFlags & expectedFlags).isEqualTo(expectedFlags);
     }
 
+    public static void assertNoFlags(int actualFlags, int expectedFlags) {
+        assertWithMessage("Flags %s in %s", expectedFlags, actualFlags)
+                .that(actualFlags & expectedFlags).isEqualTo(0);
+    }
+
     public static String callbackEventAsString(int event) {
         switch (event) {
             case AutofillCallback.EVENT_INPUT_HIDDEN:
@@ -1648,11 +1655,33 @@
     /**
      * Enable fill dialog feature
      */
-    public static  void enableFillDialogFeature(@NonNull Context context) {
+    public static void enableFillDialogFeature(@NonNull Context context) {
         DeviceConfigStateManager deviceConfigStateManager =
                 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL,
                         AutofillManager.DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED);
-        deviceConfigStateManager.set("true");
+        setDeviceConfig(deviceConfigStateManager, "true");
+    }
+
+    /**
+     * Set hints list for fill dialog
+     */
+    public static void setFillDialogHints(@NonNull Context context, @Nullable String hints) {
+        DeviceConfigStateManager deviceConfigStateManager =
+                new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL,
+                        DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS);
+        setDeviceConfig(deviceConfigStateManager, hints);
+    }
+
+    public static void setDeviceConfig(@NonNull DeviceConfigStateManager deviceConfigStateManager,
+            @Nullable String value) {
+        final String previousValue = deviceConfigStateManager.get();
+        if (TextUtils.isEmpty(value) && TextUtils.isEmpty(previousValue)
+                || TextUtils.equals(previousValue, value)) {
+            Log.v(TAG, "No changed in config: " + deviceConfigStateManager);
+            return;
+        }
+
+        deviceConfigStateManager.set(value);
     }
 
     /**
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java
index 14b7f5a..b36f393 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java
@@ -1368,4 +1368,11 @@
     private UiObject2 findFillDialogHeaderPicker() throws Exception {
         return waitForObject(FILL_DIALOG_HEADER_SELECTOR, UI_DATASET_PICKER_TIMEOUT);
     }
+
+    /**
+     * Asserts the fill dialog is not shown.
+     */
+    public void assertNoFillDialog() throws Exception {
+        assertNeverShown("Fill dialog", FILL_DIALOG_SELECTOR, DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
+    }
 }