Commit 2cbe777d authored by Clemens Arbesser's avatar Clemens Arbesser Committed by Commit Bot

[Autofill Assistant] Set trigger script UI from native.

This CL plumbs the necessary JNI calls to set trigger script UI via
native protos.

Bug: b/171776026
Change-Id: I0786aca9a88389f09b38552f7d9612943c15f40b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2524528
Commit-Queue: Clemens Arbesser <arbesser@google.com>
Reviewed-by: default avatarMarian Fechete <marianfe@google.com>
Cr-Commit-Position: refs/heads/master@{#826256}
parent 48dc977a
......@@ -201,6 +201,7 @@ generate_jni("jni_headers") {
"java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantDirectActionImpl.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantLiteService.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChip.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsModel.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/form/AssistantFormDelegate.java",
......
......@@ -382,6 +382,7 @@ public class AutofillAssistantUiController {
return chip;
}
// TODO(arbesser): Remove this and use methods in {@code AssistantChip} instead.
@CalledByNative
private static void appendChipToList(List<AssistantChip> chips, AssistantChip chip) {
chips.add(chip);
......
......@@ -8,14 +8,18 @@ import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import org.chromium.base.Callback;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
/**
* A chip to display to the user.
*/
@JNINamespace("autofill_assistant")
public class AssistantChip {
@IntDef({Type.CHIP_ASSISTIVE, Type.BUTTON_FILLED_BLUE, Type.BUTTON_HAIRLINE})
@Retention(RetentionPolicy.SOURCE)
......@@ -172,6 +176,7 @@ public class AssistantChip {
* Creates a hairline assistant chip with an empty callback. The callback needs to be bound
* before the view is inflated.
*/
@CalledByNative
public static AssistantChip createHairlineAssistantChip(
int icon, String text, boolean disabled, boolean sticky, boolean visible) {
return new AssistantChip(
......@@ -182,8 +187,19 @@ public class AssistantChip {
* Creates a blue-filled assistant chip with an empty callback. The callback needs to be bound
* before the view is inflated.
*/
@CalledByNative
public static AssistantChip createHighlightedAssistantChip(
int icon, String text, boolean disabled, boolean sticky, boolean visible) {
return new AssistantChip(Type.BUTTON_FILLED_BLUE, icon, text, disabled, sticky, visible);
}
@CalledByNative
private static List<AssistantChip> createChipList() {
return new ArrayList<>();
}
@CalledByNative
private static void addChipToList(List<AssistantChip> list, AssistantChip chip) {
list.add(chip);
}
}
......@@ -61,6 +61,9 @@ public class AssistantTriggerScript {
private final List<AssistantChip> mLeftAlignedChips = new ArrayList<>();
private final List<AssistantChip> mRightAlignedChips = new ArrayList<>();
private final List<String> mCancelPopupItems = new ArrayList<>();
private final List<Integer> mCancelPopupActions = new ArrayList<>();
private boolean mAnimateBottomSheet = true;
public AssistantTriggerScript(
......@@ -135,10 +138,6 @@ public class AssistantTriggerScript {
mAnimateBottomSheet = !disable;
}
@VisibleForTesting
public AssistantHeaderModel getHeaderModelForTest() {
return mHeaderModel;
}
@VisibleForTesting
public List<AssistantChip> getLeftAlignedChipsForTest() {
return mLeftAlignedChips;
......@@ -166,9 +165,50 @@ public class AssistantTriggerScript {
}
}
// TODO(b/171776026): before calling this method, native needs to send the necessary
// information to populate and update the views.
@VisibleForTesting
public AssistantHeaderModel getHeaderModel() {
return mHeaderModel;
}
/** Binds {@code chips} to {@code actions}. */
private void bindChips(List<AssistantChip> chips, int[] actions) {
assert chips.size() == actions.length;
for (int i = 0; i < chips.size(); ++i) {
int action = actions[i];
if (action == TriggerScriptAction.SHOW_CANCEL_POPUP) {
chips.get(i).setPopupItems(mCancelPopupItems,
result -> mDelegate.onTriggerScriptAction(mCancelPopupActions.get(result)));
} else {
chips.get(i).setSelectedListener(
() -> mDelegate.onTriggerScriptAction(actions[action]));
}
}
}
public void setLeftAlignedChips(List<AssistantChip> chips, int[] actions) {
assert chips.size() == actions.length;
mLeftAlignedChips.clear();
mLeftAlignedChips.addAll(chips);
bindChips(mLeftAlignedChips, actions);
}
public void setRightAlignedChips(List<AssistantChip> chips, int[] actions) {
assert chips.size() == actions.length;
mRightAlignedChips.clear();
mRightAlignedChips.addAll(chips);
bindChips(mRightAlignedChips, actions);
}
public void setCancelPopupMenu(String[] items, int[] actions) {
assert items.length == actions.length;
mCancelPopupItems.clear();
mCancelPopupActions.clear();
for (int i = 0; i < actions.length; ++i) {
mCancelPopupItems.add(items[i]);
mCancelPopupActions.add(actions[i]);
}
}
public void update() {
mChipsContainer.removeAllViews();
addChipsToContainer(mChipsContainer, mLeftAlignedChips);
......
......@@ -12,10 +12,13 @@ import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantClient;
import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChip;
import org.chromium.chrome.browser.autofill_assistant.header.AssistantHeaderModel;
import org.chromium.chrome.browser.autofill_assistant.metrics.LiteScriptFinishedState;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.content_public.browser.WebContents;
import java.util.List;
import java.util.Map;
/**
......@@ -27,6 +30,7 @@ public class AssistantTriggerScriptBridge {
private AssistantTriggerScript mTriggerScript;
private long mNativeBridge;
private Delegate mDelegate;
private Context mContext;
/** Interface for delegates of the {@code start} method. */
public interface Delegate {
......@@ -42,6 +46,7 @@ public class AssistantTriggerScriptBridge {
@NonNull WebContents webContents, @NonNull String initialUrl,
Map<String, String> scriptParameters, String experimentIds, Delegate delegate) {
mDelegate = delegate;
mContext = context;
mTriggerScript = new AssistantTriggerScript(context, new AssistantTriggerScript.Delegate() {
@Override
public void onTriggerScriptAction(int action) {
......@@ -71,8 +76,27 @@ public class AssistantTriggerScriptBridge {
}
@CalledByNative
private void showTriggerScript() {
// TODO update
private AssistantHeaderModel getHeaderModel() {
return mTriggerScript.getHeaderModel();
}
@CalledByNative
private Context getContext() {
return mContext;
}
/**
* Used by native to update and show the UI. The header should be updated using {@code
* getHeaderModel} prior to calling this function.
*/
@CalledByNative
private void showTriggerScript(String[] cancelPopupMenuItems, int[] cancelPopupMenuActions,
List<AssistantChip> leftAlignedChips, int[] leftAlignedChipsActions,
List<AssistantChip> rightAlignedChips, int[] rightAlignedChipsActions) {
// NOTE: the cancel popup menu must be set before the chips are bound.
mTriggerScript.setCancelPopupMenu(cancelPopupMenuItems, cancelPopupMenuActions);
mTriggerScript.setLeftAlignedChips(leftAlignedChips, leftAlignedChipsActions);
mTriggerScript.setRightAlignedChips(rightAlignedChips, rightAlignedChipsActions);
mTriggerScript.show();
}
......
......@@ -118,7 +118,7 @@ public class AutofillAssistantTriggerScriptTest {
}, getBottomSheetController()));
TestThreadUtils.runOnUiThreadBlocking(() -> {
AssistantHeaderModel headerModel = triggerScript.getHeaderModelForTest();
AssistantHeaderModel headerModel = triggerScript.getHeaderModel();
headerModel.set(AssistantHeaderModel.DISABLE_ANIMATIONS_FOR_TESTING, true);
headerModel.set(AssistantHeaderModel.STATUS_MESSAGE, "Hello world!");
headerModel.set(AssistantHeaderModel.USE_STEP_PROGRESS_BAR, true);
......
......@@ -4,7 +4,11 @@
#include "chrome/browser/android/autofill_assistant/trigger_script_bridge_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantTriggerScriptBridge_jni.h"
#include "chrome/browser/android/autofill_assistant/assistant_header_model.h"
#include "chrome/browser/android/autofill_assistant/ui_controller_android_utils.h"
#include "chrome/common/channel_info.h"
#include "components/autofill_assistant/browser/service/api_key_fetcher.h"
#include "components/autofill_assistant/browser/service/server_url_fetcher.h"
......@@ -17,6 +21,10 @@
#include "services/metrics/public/cpp/ukm_recorder.h"
using base::android::AttachCurrentThread;
using base::android::JavaParamRef;
using base::android::ScopedJavaGlobalRef;
using base::android::ToJavaArrayOfStrings;
using base::android::ToJavaIntArray;
namespace autofill_assistant {
......@@ -25,11 +33,11 @@ TriggerScriptBridgeAndroid::~TriggerScriptBridgeAndroid() = default;
void TriggerScriptBridgeAndroid::StartTriggerScript(
Client* client,
const base::android::JavaParamRef<jobject>& jdelegate,
const JavaParamRef<jobject>& jdelegate,
const GURL& initial_url,
std::unique_ptr<TriggerContext> trigger_context) {
DCHECK(!java_object_);
java_object_ = base::android::ScopedJavaGlobalRef<jobject>(jdelegate);
java_object_ = ScopedJavaGlobalRef<jobject>(jdelegate);
Java_AssistantTriggerScriptBridge_setNativePtr(
AttachCurrentThread(), java_object_, reinterpret_cast<intptr_t>(this));
......@@ -64,35 +72,40 @@ void TriggerScriptBridgeAndroid::StopTriggerScript() {
void TriggerScriptBridgeAndroid::OnTriggerScriptAction(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const JavaParamRef<jobject>& jcaller,
jint action) {
if (!trigger_script_coordinator_) {
return;
}
trigger_script_coordinator_->PerformTriggerScriptAction(
static_cast<TriggerScriptProto::TriggerScriptAction>(action));
}
void TriggerScriptBridgeAndroid::OnBottomSheetClosedWithSwipe(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller) {
const JavaParamRef<jobject>& jcaller) {
if (!trigger_script_coordinator_) {
return;
}
// TODO(b/171776026): Implement this.
}
void TriggerScriptBridgeAndroid::OnBackButtonPressed(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller) {
const JavaParamRef<jobject>& jcaller) {
if (!trigger_script_coordinator_) {
return;
}
// TODO(b/171776026): Implement this.
}
void TriggerScriptBridgeAndroid::OnFeedbackButtonClicked(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller) {
const JavaParamRef<jobject>& jcaller) {
if (!trigger_script_coordinator_) {
return;
}
// TODO(b/171776026): Implement this.
}
void TriggerScriptBridgeAndroid::OnTriggerScriptShown(
......@@ -100,8 +113,57 @@ void TriggerScriptBridgeAndroid::OnTriggerScriptShown(
if (!java_object_) {
return;
}
Java_AssistantTriggerScriptBridge_showTriggerScript(AttachCurrentThread(),
java_object_);
JNIEnv* env = AttachCurrentThread();
auto jheader_model =
Java_AssistantTriggerScriptBridge_getHeaderModel(env, java_object_);
AssistantHeaderModel header_model(jheader_model);
header_model.SetStatusMessage(proto.status_message());
header_model.SetBubbleMessage(proto.callout_message());
header_model.SetProgressVisible(proto.has_progress_bar());
if (proto.has_progress_bar()) {
ShowProgressBarProto::StepProgressBarConfiguration configuration;
configuration.set_use_step_progress_bar(true);
for (const auto& icon : proto.progress_bar().step_icons()) {
*configuration.add_annotated_step_icons()->mutable_icon() = icon;
}
auto jcontext =
Java_AssistantTriggerScriptBridge_getContext(env, java_object_);
header_model.SetStepProgressBarConfiguration(configuration, jcontext);
header_model.SetProgressActiveStep(proto.progress_bar().active_step());
}
std::vector<ChipProto> left_aligned_chips;
std::vector<int> left_aligned_chip_actions;
for (const auto& chip : proto.left_aligned_chips()) {
left_aligned_chips.emplace_back(chip.chip());
left_aligned_chip_actions.emplace_back(static_cast<int>(chip.action()));
}
auto jleft_aligned_chips =
ui_controller_android_utils::CreateJavaAssistantChipList(
env, left_aligned_chips);
std::vector<ChipProto> right_aligned_chips;
std::vector<int> right_aligned_chip_actions;
for (const auto& chip : proto.right_aligned_chips()) {
right_aligned_chips.emplace_back(chip.chip());
right_aligned_chip_actions.emplace_back(static_cast<int>(chip.action()));
}
auto jright_aligned_chips =
ui_controller_android_utils::CreateJavaAssistantChipList(
env, right_aligned_chips);
std::vector<std::string> cancel_popup_items;
std::vector<int> cancel_popup_actions;
for (const auto& choice : proto.cancel_popup().choices()) {
cancel_popup_items.emplace_back(choice.text());
cancel_popup_actions.emplace_back(static_cast<int>(choice.action()));
}
Java_AssistantTriggerScriptBridge_showTriggerScript(
env, java_object_, ToJavaArrayOfStrings(env, cancel_popup_items),
ToJavaIntArray(env, cancel_popup_actions), jleft_aligned_chips,
ToJavaIntArray(env, left_aligned_chip_actions), jright_aligned_chips,
ToJavaIntArray(env, right_aligned_chip_actions));
}
void TriggerScriptBridgeAndroid::OnTriggerScriptHidden() {
......@@ -117,8 +179,11 @@ void TriggerScriptBridgeAndroid::OnTriggerScriptFinished(
if (!java_object_) {
return;
}
// NOTE: for now, the transition to the regular script (if state == ACCEPTED)
// is still done in Java.
Java_AssistantTriggerScriptBridge_onTriggerScriptFinished(
AttachCurrentThread(), java_object_, static_cast<int>(state));
StopTriggerScript();
}
} // namespace autofill_assistant
......@@ -724,6 +724,8 @@ void UiControllerAndroid::UpdateActions(
const auto& action = user_actions[i];
const Chip& chip = action.chip();
base::android::ScopedJavaLocalRef<jobject> jchip;
// TODO(arbesser): Refactor this to use
// ui_controller_android_utils::CreateJavaAssistantChip.
switch (chip.type) {
default: // Ignore actions with other chip types or with no chips.
break;
......
......@@ -6,6 +6,7 @@
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/notreached.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantChip_jni.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantColor_jni.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantDateTime_jni.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantDialogButton_jni.h"
......@@ -423,6 +424,43 @@ int ToJavaBottomSheetState(BottomSheetState state) {
}
}
} // namespace ui_controller_android_utils
base::android::ScopedJavaLocalRef<jobject> CreateJavaAssistantChip(
JNIEnv* env,
const ChipProto& chip) {
switch (chip.type()) {
default: // Other chip types are not supported.
return nullptr;
case HIGHLIGHTED_ACTION:
case DONE_ACTION:
return Java_AssistantChip_createHighlightedAssistantChip(
env, chip.icon(),
base::android::ConvertUTF8ToJavaString(env, chip.text()),
/* disabled = */ false, chip.sticky(), /* visible = */ true);
case NORMAL_ACTION:
case CANCEL_ACTION:
case CLOSE_ACTION:
return Java_AssistantChip_createHairlineAssistantChip(
env, chip.icon(),
base::android::ConvertUTF8ToJavaString(env, chip.text()),
/* disabled = */ false, chip.sticky(), /* visible = */ true);
}
}
base::android::ScopedJavaLocalRef<jobject> CreateJavaAssistantChipList(
JNIEnv* env,
const std::vector<ChipProto>& chips) {
auto jlist = Java_AssistantChip_createChipList(env);
for (const auto& chip : chips) {
auto jchip = CreateJavaAssistantChip(env, chip);
if (!jchip) {
return nullptr;
}
Java_AssistantChip_addChipToList(env, jlist, jchip);
}
return jlist;
}
} // namespace ui_controller_android_utils
} // namespace autofill_assistant
......@@ -88,6 +88,18 @@ BottomSheetState ToNativeBottomSheetState(int state);
// Converts a BottomSheetState to the Android SheetState enum.
int ToJavaBottomSheetState(BottomSheetState state);
// Returns an instance of |AssistantChip| or nullptr if the chip type is
// invalid.
base::android::ScopedJavaLocalRef<jobject> CreateJavaAssistantChip(
JNIEnv* env,
const ChipProto& chip);
// Returns a list of |AssistantChip| instances or nullptr if any of the chips
// in |chips| has an invalid type.
base::android::ScopedJavaLocalRef<jobject> CreateJavaAssistantChipList(
JNIEnv* env,
const std::vector<ChipProto>& chips);
} // namespace ui_controller_android_utils
} // namespace autofill_assistant
......
......@@ -525,7 +525,7 @@ message TriggerScriptUIProto {
message TriggerChip {
optional ChipProto chip = 1;
optional TriggerScriptProto.TriggerScriptAction behavior = 2;
optional TriggerScriptProto.TriggerScriptAction action = 2;
}
message Popup {
message Choice {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment