Adds test case for inline suggestions tooltip

Bug: 176773156
Test: atest CtsAutoFillServiceTestCases
Change-Id: I48c3c45020b4c7f4b3f09c184be96c7ed8cd69ff
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
index 879aa8e..b206d5b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
@@ -108,7 +108,7 @@
             return false;
         }
 
-        protected static UiBot getInlineUiBot() {
+        protected static InlineUiBot getInlineUiBot() {
             return sDefaultUiBot2;
         }
 
@@ -480,7 +480,7 @@
     }
 
     protected static final UiBot sDefaultUiBot = new UiBot();
-    protected static final UiBot sDefaultUiBot2 = new InlineUiBot();
+    protected static final InlineUiBot sDefaultUiBot2 = new InlineUiBot();
 
     private AutoFillServiceTestCase() {
         throw new UnsupportedOperationException("Contain static stuff only");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineTooltipTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineTooltipTest.java
new file mode 100644
index 0000000..79095cf
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineTooltipTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2021 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.inline;
+
+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.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+
+import android.autofillservice.cts.commontests.AbstractLoginActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+/**
+ * Tests inline suggestions tooltip behaviors.
+ */
+public class InlineTooltipTest extends AbstractLoginActivityTestCase {
+
+    InlineUiBot mInlineUiBot;
+
+    public InlineTooltipTest() {
+        super(getInlineUiBot());
+        mInlineUiBot = getInlineUiBot();
+    }
+
+    @Override
+    protected boolean isInlineMode() {
+        return true;
+    }
+
+    @Override
+    public TestRule getMainTestRule() {
+        return InlineUiBot.annotateRule(super.getMainTestRule());
+    }
+
+    @Override
+    protected void enableService() {
+        Helper.enableAutofillService(getContext(), SERVICE_NAME);
+    }
+
+    @Test
+    public void testShowTooltip() throws Exception {
+        // Set service.
+        enableService();
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation(createPresentation("The Username"))
+                        .setInlinePresentation(createInlinePresentation("The Username"))
+                        .setInlineTooltipPresentation(
+                                Helper.createInlineTooltipPresentation("The Username Tooltip"))
+                        .build());
+
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("The Username");
+        mInlineUiBot.assertTooltipShowing("The Username Tooltip");
+    }
+
+    @Test
+    public void testShowTooltipWithTwoFields() throws Exception {
+        // Set service.
+        enableService();
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation(createPresentation("The Username"))
+                        .setInlinePresentation(createInlinePresentation("The Username"))
+                        .setInlineTooltipPresentation(
+                                Helper.createInlineTooltipPresentation("The Username Tooltip"))
+                        .build())
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Password"))
+                        .setInlinePresentation(createInlinePresentation("The Password"))
+                        .build())
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_PASSWORD, "lollipop")
+                        .setPresentation(createPresentation("The Password2"))
+                        .setInlinePresentation(createInlinePresentation("The Password2"))
+                        .setInlineTooltipPresentation(
+                                Helper.createInlineTooltipPresentation("The Password Tooltip"))
+                        .build());
+
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("The Username");
+        mInlineUiBot.assertTooltipShowing("The Username Tooltip");
+
+        // Switch focus to password
+        mUiBot.selectByRelativeId(ID_PASSWORD);
+        mUiBot.waitForIdleSync();
+
+        mUiBot.assertDatasets("The Password", "The Password2");
+        mInlineUiBot.assertTooltipShowing("The Password Tooltip");
+
+        // Switch focus back to username
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        mUiBot.assertDatasets("The Username");
+        mInlineUiBot.assertTooltipShowing("The Username Tooltip");
+
+        mActivity.expectAutoFill("dude");
+        mUiBot.selectDataset("The Username");
+        mUiBot.waitForIdleSync();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testShowTooltipWithSecondDataset() throws Exception {
+        // Set service.
+        enableService();
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation(createPresentation("The Username"))
+                        .setInlinePresentation(createInlinePresentation("The Username"))
+                        .build())
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "sweet")
+                        .setPresentation(createPresentation("The Username2"))
+                        .setInlinePresentation(createInlinePresentation("The Username2"))
+                        .setInlineTooltipPresentation(
+                                Helper.createInlineTooltipPresentation("The Username Tooltip"))
+                        .build())
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "candy")
+                        .setPresentation(createPresentation("The Username3"))
+                        .setInlinePresentation(createInlinePresentation("The Username3"))
+                        .build());
+
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("The Username", "The Username2", "The Username3");
+        mInlineUiBot.assertTooltipShowing("The Username Tooltip");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
index c276a7c..3ec1f12 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
@@ -603,9 +603,11 @@
         private final Map<String, AutofillValue> mFieldValues;
         private final Map<String, RemoteViews> mFieldPresentations;
         private final Map<String, InlinePresentation> mFieldInlinePresentations;
+        private final Map<String, InlinePresentation> mFieldInlineTooltipPresentations;
         private final Map<String, Pair<Boolean, Pattern>> mFieldFilters;
         private final RemoteViews mPresentation;
         private final InlinePresentation mInlinePresentation;
+        private final InlinePresentation mInlineTooltipPresentation;
         private final IntentSender mAuthentication;
         private final String mId;
 
@@ -613,9 +615,11 @@
             mFieldValues = builder.mFieldValues;
             mFieldPresentations = builder.mFieldPresentations;
             mFieldInlinePresentations = builder.mFieldInlinePresentations;
+            mFieldInlineTooltipPresentations = builder.mFieldInlineTooltipPresentations;
             mFieldFilters = builder.mFieldFilters;
             mPresentation = builder.mPresentation;
             mInlinePresentation = builder.mInlinePresentation;
+            mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
             mAuthentication = builder.mAuthentication;
             mId = builder.mId;
         }
@@ -639,12 +643,15 @@
         public Dataset asDatasetWithAutofillIdResolver(
                 Function<String, AutofillId> autofillIdResolver) {
             final Dataset.Builder builder = mPresentation != null
-                    ? mInlinePresentation == null
                     ? new Dataset.Builder(mPresentation)
-                    : new Dataset.Builder(mPresentation).setInlinePresentation(mInlinePresentation)
-                    : mInlinePresentation == null
-                            ? new Dataset.Builder()
-                            : new Dataset.Builder(mInlinePresentation);
+                    : new Dataset.Builder();
+            if (mInlinePresentation != null) {
+                if (mInlineTooltipPresentation != null) {
+                    builder.setInlinePresentation(mInlinePresentation, mInlineTooltipPresentation);
+                } else {
+                    builder.setInlinePresentation(mInlinePresentation);
+                }
+            }
 
             if (mFieldValues != null) {
                 for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) {
@@ -657,27 +664,43 @@
                     final AutofillValue value = entry.getValue();
                     final RemoteViews presentation = mFieldPresentations.get(id);
                     final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(id);
+                    final InlinePresentation tooltipPresentation =
+                            mFieldInlineTooltipPresentations.get(id);
                     final Pair<Boolean, Pattern> filter = mFieldFilters.get(id);
                     if (presentation != null) {
                         if (filter == null) {
                             if (inlinePresentation != null) {
-                                builder.setValue(autofillId, value, presentation,
-                                        inlinePresentation);
+                                if (tooltipPresentation != null) {
+                                    builder.setValue(autofillId, value, presentation,
+                                            inlinePresentation, tooltipPresentation);
+                                } else {
+                                    builder.setValue(autofillId, value, presentation,
+                                            inlinePresentation);
+                                }
                             } else {
                                 builder.setValue(autofillId, value, presentation);
                             }
                         } else {
                             if (inlinePresentation != null) {
-                                builder.setValue(autofillId, value, filter.second, presentation,
-                                        inlinePresentation);
+                                if (tooltipPresentation != null) {
+                                    builder.setValue(autofillId, value, filter.second, presentation,
+                                            inlinePresentation, tooltipPresentation);
+                                } else {
+                                    builder.setValue(autofillId, value, filter.second, presentation,
+                                            inlinePresentation);
+                                }
                             } else {
                                 builder.setValue(autofillId, value, filter.second, presentation);
                             }
                         }
                     } else {
                         if (inlinePresentation != null) {
-                            builder.setFieldInlinePresentation(autofillId, value,
-                                    filter != null ? filter.second : null, inlinePresentation);
+                            if (tooltipPresentation != null) {
+                                throw new IllegalStateException("presentation can not be null");
+                            } else {
+                                builder.setFieldInlinePresentation(autofillId, value,
+                                        filter != null ? filter.second : null, inlinePresentation);
+                            }
                         } else {
                             if (filter == null) {
                                 builder.setValue(autofillId, value);
@@ -698,6 +721,7 @@
                     + ", hasInlinePresentation=" + (mInlinePresentation != null)
                     + ", fieldPresentations=" + (mFieldPresentations)
                     + ", fieldInlinePresentations=" + (mFieldInlinePresentations)
+                    + ", fieldTooltipInlinePresentations=" + (mFieldInlineTooltipPresentations)
                     + ", hasAuthentication=" + (mAuthentication != null)
                     + ", fieldValues=" + mFieldValues
                     + ", fieldFilters=" + mFieldFilters + "]";
@@ -708,12 +732,15 @@
             private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>();
             private final Map<String, InlinePresentation> mFieldInlinePresentations =
                     new HashMap<>();
+            private final Map<String, InlinePresentation> mFieldInlineTooltipPresentations =
+                    new HashMap<>();
             private final Map<String, Pair<Boolean, Pattern>> mFieldFilters = new HashMap<>();
 
             private RemoteViews mPresentation;
             private InlinePresentation mInlinePresentation;
             private IntentSender mAuthentication;
             private String mId;
+            private InlinePresentation mInlineTooltipPresentation;
 
             public Builder() {
 
@@ -858,6 +885,21 @@
              * {@link IdMode}.
              */
             public Builder setField(String id, String text, RemoteViews presentation,
+                    InlinePresentation inlinePresentation,
+                    InlinePresentation inlineTooltipPresentation) {
+                setField(id, text, presentation, inlinePresentation);
+                mFieldInlineTooltipPresentations.put(id, inlineTooltipPresentation);
+                return this;
+            }
+
+            /**
+             * Sets the canned value of a field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, String text, RemoteViews presentation,
                     InlinePresentation inlinePresentation, Pattern filter) {
                 setField(id, text, presentation, inlinePresentation);
                 mFieldFilters.put(id, new Pair<>(true, filter));
@@ -865,6 +907,23 @@
             }
 
             /**
+             * Sets the canned value of a field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, String text, RemoteViews presentation,
+                    InlinePresentation inlinePresentation,
+                    InlinePresentation inlineTooltipPresentation,
+                    Pattern filter) {
+                setField(id, text, presentation, inlinePresentation, inlineTooltipPresentation);
+                mFieldFilters.put(id, new Pair<>(true, filter));
+
+                return this;
+            }
+
+            /**
              * Sets the view to present the response in the UI.
              */
             public Builder setPresentation(RemoteViews presentation) {
@@ -880,6 +939,14 @@
                 return this;
             }
 
+            /**
+             * Sets the inline tooltip to present the response in the UI.
+             */
+            public Builder setInlineTooltipPresentation(InlinePresentation tooltip) {
+                mInlineTooltipPresentation = tooltip;
+                return this;
+            }
+
             public Builder setPresentation(String message, boolean inlineMode) {
                 mPresentation = createPresentation(message);
                 if (inlineMode) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
index a3097c8..46a613f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
@@ -1549,6 +1549,22 @@
                         .build(), /* pinned= */ pinned);
     }
 
+    public static InlinePresentation createInlineTooltipPresentation(
+            @NonNull String message) {
+        final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
+        return createInlineTooltipPresentation(message, dummyIntent);
+    }
+
+    private static InlinePresentation createInlineTooltipPresentation(
+            @NonNull String message, @NonNull PendingIntent attribution) {
+        return InlinePresentation.createTooltipPresentation(
+                InlineSuggestionUi.newContentBuilder(attribution)
+                        .setTitle(message).build().getSlice(),
+                new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100))
+                        .build());
+    }
+
     public static void mockSwitchInputMethod(@NonNull Context context) throws Exception {
         final ContentResolver cr = context.getContentResolver();
         final int subtype = Settings.Secure.getInt(cr, SELECTED_INPUT_METHOD_SUBTYPE);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java
index 88f97c2..50892fc 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java
@@ -126,6 +126,13 @@
         strip.fling(direction, speed);
     }
 
+    public void assertTooltipShowing(String text) throws Exception {
+        final UiObject2 strip = waitForObject(By.text(text), UI_TIMEOUT);
+        if (strip == null) {
+            throw new AssertionError("not find inline tooltip by text: " + text);
+        }
+    }
+
     private UiObject2 findSuggestionStrip(Timeout timeout) throws Exception {
         return waitForObject(SUGGESTION_STRIP_SELECTOR, timeout);
     }
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index 01e4a82..182bb11 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -865,8 +865,12 @@
             presentationSpecs.add(new InlinePresentationSpec.Builder(new Size(100, 100),
                     new Size(400, 100)).setStyle(styles).build());
 
+            final InlinePresentationSpec tooltipSpec =
+                    new InlinePresentationSpec.Builder(new Size(100, 100),
+                            new Size(400, 100)).setStyle(styles).build();
             final InlineSuggestionsRequest.Builder builder =
                     new InlineSuggestionsRequest.Builder(presentationSpecs)
+                            .setInlineTooltipPresentationSpec(tooltipSpec)
                             .setMaxSuggestionCount(6);
             if (mInlineSuggestionsExtras != null) {
                 builder.setExtras(mInlineSuggestionsExtras.deepCopy());