[Autofill Assistant] Support highlighted button in PromptAction.

Before this change, PromptAction only supported normal chips. This
change adds support for displaying choice as scripts in buttons with
highlights.

The way this is done reproduces exactly the way buttons are configured
for scripts, sharing the same logic and code. The idea is that it would
help keep the two consistent.

Bug: 806868
Change-Id: I82b3a49b1339e4aecd1c3dffabb826022fbac1e6
Reviewed-on: https://chromium-review.googlesource.com/c/1348050
Commit-Queue: Stephane Zermatten <szermatt@chromium.org>
Reviewed-by: Mathias Carlen <mcarlen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#610648}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
index 103e459f..361fe03 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
@@ -222,19 +222,22 @@
         assert scriptNames.length == scriptPaths.length;
         assert scriptNames.length == scriptsHighlightFlags.length;
 
-        ArrayList<AutofillAssistantUiDelegate.ScriptHandle> scriptHandles = new ArrayList<>();
-        // Note that scriptNames, scriptPaths and scriptsHighlightFlags are one-on-one matched by
+        List<AutofillAssistantUiDelegate.ScriptHandle> scriptHandles = new ArrayList<>();
+        // Note that scriptNames, scriptsHighlightFlags and scriptPaths are one-on-one matched by
         // index.
         for (int i = 0; i < scriptNames.length; i++) {
             scriptHandles.add(new AutofillAssistantUiDelegate.ScriptHandle(
-                    scriptNames[i], scriptPaths[i], scriptsHighlightFlags[i]));
+                    scriptNames[i], scriptsHighlightFlags[i], scriptPaths[i]));
         }
 
         mUiDelegateHolder.performUiOperation(uiDelegate -> uiDelegate.updateScripts(scriptHandles));
     }
 
     @CalledByNative
-    private void onChoose(String[] names, byte[][] serverPayloads) {
+    private void onChoose(String[] names, byte[][] serverPayloads, boolean[] highlightFlags) {
+        assert names.length == serverPayloads.length;
+        assert names.length == highlightFlags.length;
+
         // An empty choice list is supported, as selection can still be forced. onForceChoose should
         // be a no-op in this case.
         if (names.length == 0) return;
@@ -242,7 +245,8 @@
         List<AutofillAssistantUiDelegate.Choice> choices = new ArrayList<>();
         assert (names.length == serverPayloads.length);
         for (int i = 0; i < names.length; i++) {
-            choices.add(new AutofillAssistantUiDelegate.Choice(names[i], serverPayloads[i]));
+            choices.add(new AutofillAssistantUiDelegate.Choice(
+                    names[i], highlightFlags[i], serverPayloads[i]));
         }
         mUiDelegateHolder.performUiOperation(uiDelegate -> uiDelegate.showChoices(choices));
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiDelegate.java
index dc9111f..f51a4b1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiDelegate.java
@@ -181,59 +181,62 @@
         void onInitOk();
     }
 
-    /**
-     * Java side equivalent of autofill_assistant::ScriptHandle.
-     */
-    protected static class ScriptHandle {
-        /** The display name of this script. */
+    /** Describes a chip to display. */
+    static class Chip {
         private final String mName;
-        /** The script path. */
-        private final String mPath;
-        /** Whether the script should be highlighted. */
         private final boolean mHighlight;
 
-        /** Constructor. */
-        public ScriptHandle(String name, String path, boolean highlight) {
-            mName = name;
-            mPath = path;
-            mHighlight = highlight;
-        }
-
-        /** Returns the display name. */
-        public String getName() {
-            return mName;
-        }
-
-        /** Returns the script path. */
-        public String getPath() {
-            return mPath;
-        }
-
-        /** Returns whether the script should be highlighted. */
-        public boolean isHighlight() {
-            return mHighlight;
-        }
-    }
-
-    /** A choice to pass to {@link AutofillAssistantUiDelegate#onChoose}. */
-    static class Choice {
-        private final String mName;
-        private final byte[] mServerPayload;
-
         /** Returns the localized name to display. */
         String getName() {
             return mName;
         }
 
-        /** Returns the server payload associated with this choice, to pass to the callback. */
-        byte[] getServerPayload() {
-            return mServerPayload;
+        /** Returns {@code true} if the choice should be highlight. */
+        boolean isHighlight() {
+            return mHighlight;
         }
 
-        Choice(String name, byte[] serverPayload) {
+        Chip(String name, boolean highlight) {
             mName = name;
+            mHighlight = highlight;
+        }
+    }
+
+    /** Functional interface that acts on a chip. */
+    interface ChipAction<T extends Chip> {
+        void apply(T chip);
+    }
+
+    /** Java side equivalent of autofill_assistant::ScriptHandle. */
+    static class ScriptHandle extends Chip {
+        private final String mPath;
+
+        /** Constructor. */
+        ScriptHandle(String name, boolean highlight, String path) {
+            super(name, highlight);
+            mPath = path;
+        }
+
+        /** Returns the script path. */
+        String getPath() {
+            return mPath;
+        }
+    }
+
+    /** Java side equivalent of autofill_assistant::UiController::Choice. */
+    static class Choice extends Chip {
+        private final byte[] mServerPayload;
+
+        /** Constructor. */
+        Choice(String name, boolean highlight, byte[] serverPayload) {
+            super(name, highlight);
             mServerPayload = serverPayload;
         }
+
+        /** Returns the serverPayload that corresponds to that choice. */
+        byte[] getServerPayload() {
+            return mServerPayload;
+        }
     }
 
     // Names borrowed from :
@@ -319,34 +322,36 @@
      *
      * @param scriptHandles List of scripts to show.
      */
-    public void updateScripts(ArrayList<ScriptHandle> scriptHandles) {
+    public void updateScripts(List<ScriptHandle> scriptHandles) {
         if (scriptHandles.isEmpty()) {
             clearCarousel();
             return;
         }
 
-        boolean alignRight = hasHighlightedScript(scriptHandles);
+        addChips(scriptHandles, scriptHandle -> {
+            clearCarousel();
+            mClient.onScriptSelected(scriptHandle.getPath());
+        });
+    }
+
+    private <T extends Chip> void addChips(Iterable<T> chips, ChipAction<T> onClick) {
+        boolean alignRight = hasHighlightedScript(chips);
         @ChipStyle
         int nonHighlightStyle = alignRight ? ChipStyle.BUTTON_HAIRLINE : ChipStyle.CHIP_ASSISTIVE;
-        ArrayList<View> childViews = new ArrayList<>();
-        for (int i = 0; i < scriptHandles.size(); i++) {
-            ScriptHandle scriptHandle = scriptHandles.get(i);
+        List<View> childViews = new ArrayList<>();
+        for (T chip : chips) {
             @ChipStyle
-            int chipStyle =
-                    scriptHandle.isHighlight() ? ChipStyle.BUTTON_FILLED : nonHighlightStyle;
-            TextView chipView = createChipView(scriptHandle.getName(), chipStyle);
-            chipView.setOnClickListener((unusedView) -> {
-                clearCarousel();
-                mClient.onScriptSelected(scriptHandle.getPath());
-            });
+            int chipStyle = chip.isHighlight() ? ChipStyle.BUTTON_FILLED : nonHighlightStyle;
+            TextView chipView = createChipView(chip.getName(), chipStyle);
+            chipView.setOnClickListener((unusedView) -> onClick.apply(chip));
             childViews.add(chipView);
         }
         setCarouselChildViews(childViews, alignRight);
     }
 
-    private boolean hasHighlightedScript(ArrayList<ScriptHandle> scripts) {
-        for (int i = 0; i < scripts.size(); i++) {
-            if (scripts.get(i).isHighlight()) {
+    private boolean hasHighlightedScript(Iterable<? extends Chip> chips) {
+        for (Chip chip : chips) {
+            if (chip.isHighlight()) {
                 return true;
             }
         }
@@ -693,16 +698,10 @@
 
     /** Shows chip with the given choices. */
     public void showChoices(List<Choice> choices) {
-        List<View> childViews = new ArrayList<>();
-        for (Choice choice : choices) {
-            TextView chipView = createChipView(choice.getName(), ChipStyle.CHIP_ASSISTIVE);
-            chipView.setOnClickListener(unusedView -> {
-                clearCarousel();
-                mClient.onChoice(choice.getServerPayload());
-            });
-            childViews.add(chipView);
-        }
-        setCarouselChildViews(childViews, /* alignRight= */ false);
+        addChips(choices, choice -> {
+            clearCarousel();
+            mClient.onChoice(choice.getServerPayload());
+        });
     }
 
     /**
diff --git a/chrome/browser/android/autofill_assistant/ui_controller_android.cc b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
index 3210eab..f1be6789 100644
--- a/chrome/browser/android/autofill_assistant/ui_controller_android.cc
+++ b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
@@ -324,15 +324,19 @@
 
   std::vector<std::string> names;
   std::vector<std::string> server_payload;
+  bool highlights[choices.size()];
+  int i = 0;
   for (const auto& choice : choices) {
     names.emplace_back(choice.name);
     server_payload.emplace_back(choice.server_payload);
+    highlights[i++] = choice.highlight;
   }
   JNIEnv* env = AttachCurrentThread();
   Java_AutofillAssistantUiController_onChoose(
       env, java_autofill_assistant_ui_controller_,
       base::android::ToJavaArrayOfStrings(env, names),
-      base::android::ToJavaArrayOfByteArray(env, server_payload));
+      base::android::ToJavaArrayOfByteArray(env, server_payload),
+      base::android::ToJavaBooleanArray(env, highlights, choices.size()));
 }
 
 void UiControllerAndroid::ForceChoose(const std::string& result) {
diff --git a/components/autofill_assistant/browser/actions/prompt_action.cc b/components/autofill_assistant/browser/actions/prompt_action.cc
index 5d6e955..3b96fe08 100644
--- a/components/autofill_assistant/browser/actions/prompt_action.cc
+++ b/components/autofill_assistant/browser/actions/prompt_action.cc
@@ -35,6 +35,7 @@
     choices.emplace_back();
     auto& choice = choices.back();
     choice.name = choice_proto.name();
+    choice.highlight = choice_proto.highlight();
     choice_proto.SerializeToString(&choice.server_payload);
   }
   delegate->Choose(choices, base::BindOnce(&PromptAction::OnSuggestionChosen,
diff --git a/components/autofill_assistant/browser/service.proto b/components/autofill_assistant/browser/service.proto
index b5f7cc6..7f177bd 100644
--- a/components/autofill_assistant/browser/service.proto
+++ b/components/autofill_assistant/browser/service.proto
@@ -449,13 +449,14 @@
   // One of these protos must is transmitted as-is back to the server as part of
   // ProcessedActionProto.
   message Choice {
-    oneof type {
-      // A localized text message to display.
-      string name = 2;
+    // Localized text message to display. Not required if element_exists is set.
+    optional string name = 2;
 
-      // Auto-select if the given element exist.
-      ElementReferenceProto element_exists = 4;
-    }
+    // If set, the choice should be highlighted. Ignored unless name is set.
+    optional bool highlight = 3;
+
+    // Optionally auto-select this choice if the given element exist.
+    optional ElementReferenceProto element_exists = 4;
 
     // Server payload originally sent by the server. This should
     // be transmitted as-is by the client without interpreting.
diff --git a/components/autofill_assistant/browser/ui_controller.h b/components/autofill_assistant/browser/ui_controller.h
index f699834..6ceeaa20 100644
--- a/components/autofill_assistant/browser/ui_controller.h
+++ b/components/autofill_assistant/browser/ui_controller.h
@@ -27,6 +27,9 @@
     // Localized string to display.
     std::string name;
 
+    // If true, highlight this choice in the UI.
+    bool highlight = false;
+
     // Opaque data to send back to the callback. Not necessarily a UTF8 string.
     std::string server_payload;
   };