blob: 98f058f6f5f86d835d7162df8c967cbe1f307a69 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/android/autofill_assistant/ui_controller_android.h"
#include <map>
#include <memory>
#include <utility>
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/metrics/field_trial_params.h"
#include "base/optional.h"
#include "base/task/post_task.h"
#include "base/time/time.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantCollectUserDataModel_jni.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantDetailsModel_jni.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantDetails_jni.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantFormInput_jni.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantFormModel_jni.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantGenericUiModel_jni.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantInfoBoxModel_jni.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantInfoBox_jni.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantModel_jni.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AssistantOverlayModel_jni.h"
#include "chrome/android/features/autofill_assistant/jni_headers/AutofillAssistantUiController_jni.h"
#include "chrome/browser/android/autofill_assistant/client_android.h"
#include "chrome/browser/android/autofill_assistant/generic_ui_root_controller_android.h"
#include "chrome/browser/android/autofill_assistant/ui_controller_android_utils.h"
#include "chrome/browser/autofill/android/personal_data_manager_android.h"
#include "chrome/browser/autofill/personal_data_manager_factory.h"
#include "chrome/browser/flags/android/chrome_feature_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/common/channel_info.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/data_model/credit_card.h"
#include "components/autofill_assistant/browser/bottom_sheet_state.h"
#include "components/autofill_assistant/browser/client_settings.h"
#include "components/autofill_assistant/browser/controller.h"
#include "components/autofill_assistant/browser/event_handler.h"
#include "components/autofill_assistant/browser/features.h"
#include "components/autofill_assistant/browser/metrics.h"
#include "components/autofill_assistant/browser/rectf.h"
#include "components/autofill_assistant/browser/user_data.h"
#include "components/autofill_assistant/browser/user_data_util.h"
#include "components/autofill_assistant/browser/user_model.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/strings/grit/components_strings.h"
#include "components/version_info/channel.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "google_apis/google_api_keys.h"
#include "ui/base/l10n/l10n_util.h"
using base::android::AttachCurrentThread;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaParamRef;
using base::android::JavaRef;
namespace autofill_assistant {
namespace {
std::vector<float> ToFloatVector(const std::vector<RectF>& areas) {
std::vector<float> flattened;
for (const auto& rect : areas) {
flattened.emplace_back(rect.left);
flattened.emplace_back(rect.top);
flattened.emplace_back(rect.right);
flattened.emplace_back(rect.bottom);
}
return flattened;
}
base::android::ScopedJavaLocalRef<jobject> CreateJavaDateTime(
JNIEnv* env,
const DateTimeProto& proto) {
return Java_AssistantCollectUserDataModel_createAssistantDateTime(
env, (int)proto.date().year(), proto.date().month(), proto.date().day(),
proto.time().hour(), proto.time().minute(), proto.time().second());
}
base::android::ScopedJavaLocalRef<jobject> CreateJavaDate(
JNIEnv* env,
const DateProto& proto) {
DateTimeProto date_time;
*date_time.mutable_date() = proto;
return CreateJavaDateTime(env, date_time);
}
// Creates the Java equivalent to |login_choices|.
base::android::ScopedJavaLocalRef<jobject> CreateJavaLoginChoiceList(
JNIEnv* env,
const std::vector<LoginChoice>& login_choices) {
auto jlist = Java_AssistantCollectUserDataModel_createLoginChoiceList(env);
for (const auto& login_choice : login_choices) {
base::android::ScopedJavaLocalRef<jobject> jinfo_popup = nullptr;
if (login_choice.info_popup.has_value()) {
jinfo_popup = ui_controller_android_utils::CreateJavaInfoPopup(
env, *login_choice.info_popup);
}
base::android::ScopedJavaLocalRef<jstring> jsublabel_accessibility_hint =
nullptr;
if (login_choice.sublabel_accessibility_hint.has_value()) {
jsublabel_accessibility_hint = ConvertUTF8ToJavaString(
env, login_choice.sublabel_accessibility_hint.value());
}
base::android::ScopedJavaLocalRef<jstring>
jedit_button_content_description = nullptr;
if (login_choice.edit_button_content_description.has_value()) {
jedit_button_content_description = base::android::ConvertUTF8ToJavaString(
env, login_choice.edit_button_content_description.value());
}
Java_AssistantCollectUserDataModel_addLoginChoice(
env, jlist, ConvertUTF8ToJavaString(env, login_choice.identifier),
ConvertUTF8ToJavaString(env, login_choice.label),
ConvertUTF8ToJavaString(env, login_choice.sublabel),
jsublabel_accessibility_hint, login_choice.preselect_priority,
jinfo_popup, jedit_button_content_description);
}
return jlist;
}
// Creates the java equivalent to the text inputs specified in |section|.
base::android::ScopedJavaLocalRef<jobject> CreateJavaTextInputsForSection(
JNIEnv* env,
const TextInputSectionProto& section) {
auto jinput_list =
Java_AssistantCollectUserDataModel_createTextInputList(env);
for (const auto& input : section.input_fields()) {
TextInputType type;
switch (input.input_type()) {
case TextInputProto::INPUT_TEXT:
type = TextInputType::INPUT_TEXT;
break;
case TextInputProto::INPUT_ALPHANUMERIC:
type = TextInputType::INPUT_ALPHANUMERIC;
break;
case TextInputProto::UNDEFINED:
NOTREACHED();
continue;
}
Java_AssistantCollectUserDataModel_appendTextInput(
env, jinput_list, type, ConvertUTF8ToJavaString(env, input.hint()),
ConvertUTF8ToJavaString(env, input.value()),
ConvertUTF8ToJavaString(env, input.client_memory_key()));
}
return jinput_list;
}
// Creates the java equivalent to |sections|.
base::android::ScopedJavaLocalRef<jobject> CreateJavaAdditionalSections(
JNIEnv* env,
const std::vector<UserFormSectionProto>& sections) {
auto jsection_list =
Java_AssistantCollectUserDataModel_createAdditionalSectionsList(env);
for (const auto& section : sections) {
switch (section.section_case()) {
case UserFormSectionProto::kStaticTextSection:
Java_AssistantCollectUserDataModel_appendStaticTextSection(
env, jsection_list, ConvertUTF8ToJavaString(env, section.title()),
ConvertUTF8ToJavaString(env, section.static_text_section().text()));
break;
case UserFormSectionProto::kTextInputSection: {
Java_AssistantCollectUserDataModel_appendTextInputSection(
env, jsection_list, ConvertUTF8ToJavaString(env, section.title()),
CreateJavaTextInputsForSection(env, section.text_input_section()));
break;
}
case UserFormSectionProto::kPopupListSection: {
std::vector<std::string> items;
std::copy(section.popup_list_section().item_names().begin(),
section.popup_list_section().item_names().end(),
std::back_inserter(items));
std::vector<int> initial_selections;
std::copy(section.popup_list_section().initial_selection().begin(),
section.popup_list_section().initial_selection().end(),
std::back_inserter(initial_selections));
Java_AssistantCollectUserDataModel_appendPopupListSection(
env, jsection_list, ConvertUTF8ToJavaString(env, section.title()),
ConvertUTF8ToJavaString(
env, section.popup_list_section().additional_value_key()),
base::android::ToJavaArrayOfStrings(env, items),
base::android::ToJavaIntArray(env, initial_selections),
section.popup_list_section().allow_multiselect(),
section.popup_list_section().selection_mandatory(),
ConvertUTF8ToJavaString(
env,
section.popup_list_section().no_selection_error_message()));
break;
}
case UserFormSectionProto::SECTION_NOT_SET:
NOTREACHED();
break;
}
}
return jsection_list;
}
base::Optional<int> GetPreviousFormCounterResult(
const FormProto::Result* result,
int input_index,
int counter_index) {
if (result == nullptr) {
return base::nullopt;
}
if (input_index >= result->input_results().size()) {
return base::nullopt;
}
auto input_result = result->input_results(input_index);
if (counter_index >= input_result.counter().values().size()) {
return base::nullopt;
}
return input_result.counter().values(counter_index);
}
base::Optional<bool> GetPreviousFormSelectionResult(
const FormProto::Result* result,
int input_index,
int selection_index) {
if (result == nullptr) {
return base::nullopt;
}
if (input_index >= result->input_results().size()) {
return base::nullopt;
}
auto input_result = result->input_results(input_index);
if (selection_index >= input_result.selection().selected().size()) {
return base::nullopt;
}
return input_result.selection().selected(selection_index);
}
bool ShouldAllowSoftKeyboardForState(AutofillAssistantState state) {
switch (state) {
case AutofillAssistantState::STARTING:
case AutofillAssistantState::RUNNING:
return false;
case AutofillAssistantState::AUTOSTART_FALLBACK_PROMPT:
case AutofillAssistantState::PROMPT:
case AutofillAssistantState::BROWSE:
case AutofillAssistantState::MODAL_DIALOG:
case AutofillAssistantState::STOPPED:
case AutofillAssistantState::TRACKING:
case AutofillAssistantState::INACTIVE:
return true;
}
}
} // namespace
// static
std::unique_ptr<UiControllerAndroid> UiControllerAndroid::CreateFromWebContents(
content::WebContents* web_contents,
const base::android::JavaParamRef<jobject>& joverlay_coordinator) {
JNIEnv* env = AttachCurrentThread();
auto jactivity = Java_AutofillAssistantUiController_findAppropriateActivity(
env, web_contents->GetJavaWebContents());
if (!jactivity) {
return nullptr;
}
return std::make_unique<UiControllerAndroid>(env, jactivity,
joverlay_coordinator);
}
UiControllerAndroid::UiControllerAndroid(
JNIEnv* env,
const base::android::JavaRef<jobject>& jactivity,
const base::android::JavaParamRef<jobject>& joverlay_coordinator)
: overlay_delegate_(this),
header_delegate_(this),
collect_user_data_delegate_(this),
form_delegate_(this),
generic_ui_delegate_(this),
bottom_bar_delegate_(this) {
java_object_ = Java_AutofillAssistantUiController_create(
env, jactivity,
/* allowTabSwitching= */
base::FeatureList::IsEnabled(features::kAutofillAssistantChromeEntry),
reinterpret_cast<intptr_t>(this), joverlay_coordinator);
header_model_ = std::make_unique<AssistantHeaderModel>(
Java_AssistantModel_getHeaderModel(env, GetModel()));
// Register overlay_delegate_ as delegate for the overlay.
Java_AssistantOverlayModel_setDelegate(env, GetOverlayModel(),
overlay_delegate_.GetJavaObject());
// Register header_delegate_ as delegate for clicks on header buttons.
header_model_->SetDelegate(header_delegate_);
// Register collect_user_data_delegate_ as delegate for the collect user data
// UI.
Java_AssistantCollectUserDataModel_setDelegate(
env, GetCollectUserDataModel(),
collect_user_data_delegate_.GetJavaObject());
Java_AssistantModel_setBottomBarDelegate(
env, GetModel(), bottom_bar_delegate_.GetJavaObject());
}
void UiControllerAndroid::Attach(content::WebContents* web_contents,
ClientAndroid* client,
UiDelegate* ui_delegate) {
DCHECK(web_contents);
DCHECK(client);
DCHECK(ui_delegate);
client_ = client;
// Detach from the current ui_delegate, if one was set previously.
Detach();
// Attach to the new ui_delegate.
ui_delegate_ = ui_delegate;
ui_delegate_->AddObserver(this);
JNIEnv* env = AttachCurrentThread();
auto java_web_contents = web_contents->GetJavaWebContents();
Java_AutofillAssistantUiController_setWebContents(env, java_object_,
java_web_contents);
Java_AssistantCollectUserDataModel_setWebContents(
env, GetCollectUserDataModel(), java_web_contents);
OnClientSettingsChanged(ui_delegate_->GetClientSettings());
Java_AssistantModel_setPeekModeDisabled(env, GetModel(),
ui_delegate->IsRunningLiteScript());
if (ui_delegate->GetState() != AutofillAssistantState::INACTIVE &&
ui_delegate->IsTabSelected()) {
// The UI was created for an existing Controller.
RestoreUi();
} else if (ui_delegate->GetState() == AutofillAssistantState::INACTIVE) {
SetVisible(true);
}
// The call to set the web contents will, for some edge cases, trigger a call
// from the Java side to the onTabSelected method.
// We want this to happen only after the AttachUI method was fully executed,
// as it would otherwise find that IsTabSelected() is true when deciding if
// restoring the UI.
Java_AssistantModel_setWebContents(env, GetModel(), java_web_contents);
}
void UiControllerAndroid::Detach() {
if (ui_delegate_) {
ui_delegate_->RemoveObserver(this);
}
ui_delegate_ = nullptr;
}
UiControllerAndroid::~UiControllerAndroid() {
Java_AutofillAssistantUiController_clearNativePtr(AttachCurrentThread(),
java_object_);
if (ui_delegate_) {
ui_delegate_->SetUiShown(false);
}
Detach();
}
base::android::ScopedJavaLocalRef<jobject> UiControllerAndroid::GetModel() {
return Java_AutofillAssistantUiController_getModel(AttachCurrentThread(),
java_object_);
}
void UiControllerAndroid::OnStateChanged(AutofillAssistantState new_state) {
if (!Java_AssistantModel_getVisible(AttachCurrentThread(), GetModel())) {
// Leave the UI alone as long as it's invisible. Missed state changes will
// be recovered by SetVisible(true).
return;
}
SetupForState();
}
void UiControllerAndroid::SetupForState() {
DCHECK(ui_delegate_ != nullptr);
UpdateActions(ui_delegate_->GetUserActions());
AutofillAssistantState state = ui_delegate_->GetState();
AllowShowingSoftKeyboard(ShouldAllowSoftKeyboardForState(state));
bool should_prompt_action_expand_sheet =
ui_delegate_->ShouldPromptActionExpandSheet();
switch (state) {
case AutofillAssistantState::STARTING:
SetOverlayState(OverlayState::FULL);
SetSpinPoodle(true);
return;
case AutofillAssistantState::RUNNING:
SetOverlayState(OverlayState::FULL);
SetSpinPoodle(true);
return;
case AutofillAssistantState::AUTOSTART_FALLBACK_PROMPT:
SetOverlayState(OverlayState::HIDDEN);
SetSpinPoodle(false);
if (should_prompt_action_expand_sheet && ui_delegate_->IsTabSelected())
ShowContentAndExpandBottomSheet();
return;
case AutofillAssistantState::PROMPT:
SetOverlayState(OverlayState::PARTIAL);
SetSpinPoodle(false);
if (should_prompt_action_expand_sheet && ui_delegate_->IsTabSelected())
ShowContentAndExpandBottomSheet();
return;
case AutofillAssistantState::BROWSE:
SetOverlayState(OverlayState::HIDDEN);
SetSpinPoodle(false);
return;
case AutofillAssistantState::MODAL_DIALOG:
SetOverlayState(OverlayState::FULL);
SetSpinPoodle(true);
return;
case AutofillAssistantState::STOPPED:
SetOverlayState(OverlayState::HIDDEN);
SetSpinPoodle(false);
// Make sure the user sees the error message.
if (ui_delegate_->IsTabSelected())
ShowContentAndExpandBottomSheet();
ResetGenericUiControllers();
return;
case AutofillAssistantState::TRACKING:
SetOverlayState(OverlayState::HIDDEN);
SetSpinPoodle(false);
Java_AssistantModel_setVisible(AttachCurrentThread(), GetModel(), false);
DestroySelf();
return;
case AutofillAssistantState::INACTIVE:
// Wait for the state to change.
return;
}
NOTREACHED() << "Unknown state: " << static_cast<int>(state);
}
void UiControllerAndroid::OnStatusMessageChanged(const std::string& message) {
header_model_->SetStatusMessage(message);
}
void UiControllerAndroid::OnBubbleMessageChanged(const std::string& message) {
if (!message.empty()) {
header_model_->SetBubbleMessage(message);
}
}
void UiControllerAndroid::OnProgressChanged(int progress) {
header_model_->SetProgress(progress);
}
void UiControllerAndroid::OnProgressActiveStepChanged(int active_step) {
header_model_->SetProgressActiveStep(active_step);
}
void UiControllerAndroid::OnProgressVisibilityChanged(bool visible) {
header_model_->SetProgressVisible(visible);
}
void UiControllerAndroid::OnProgressBarErrorStateChanged(bool error) {
header_model_->SetProgressBarErrorState(error);
}
void UiControllerAndroid::OnStepProgressBarConfigurationChanged(
const ShowProgressBarProto::StepProgressBarConfiguration& configuration) {
header_model_->SetStepProgressBarConfiguration(
configuration, Java_AutofillAssistantUiController_getContext(
AttachCurrentThread(), java_object_));
}
void UiControllerAndroid::OnViewportModeChanged(ViewportMode mode) {
Java_AutofillAssistantUiController_setViewportMode(AttachCurrentThread(),
java_object_, mode);
}
void UiControllerAndroid::OnPeekModeChanged(
ConfigureBottomSheetProto::PeekMode peek_mode) {
Java_AutofillAssistantUiController_setPeekMode(AttachCurrentThread(),
java_object_, peek_mode);
}
void UiControllerAndroid::OnExpandBottomSheet() {
Java_AutofillAssistantUiController_expandBottomSheet(AttachCurrentThread(),
java_object_);
}
void UiControllerAndroid::OnCollapseBottomSheet() {
Java_AutofillAssistantUiController_collapseBottomSheet(AttachCurrentThread(),
java_object_);
}
void UiControllerAndroid::OnOverlayColorsChanged(
const UiDelegate::OverlayColors& colors) {
JNIEnv* env = AttachCurrentThread();
auto overlay_model = GetOverlayModel();
Java_AssistantOverlayModel_setBackgroundColor(
env, overlay_model,
ui_controller_android_utils::GetJavaColor(env, colors.background));
Java_AssistantOverlayModel_setHighlightBorderColor(
env, overlay_model,
ui_controller_android_utils::GetJavaColor(env, colors.highlight_border));
}
void UiControllerAndroid::AllowShowingSoftKeyboard(bool enabled) {
Java_AssistantModel_setAllowSoftKeyboard(AttachCurrentThread(), GetModel(),
enabled);
}
void UiControllerAndroid::ShowContentAndExpandBottomSheet() {
Java_AutofillAssistantUiController_showContentAndExpandBottomSheet(
AttachCurrentThread(), java_object_);
}
void UiControllerAndroid::SetSpinPoodle(bool enabled) {
header_model_->SetSpinPoodle(enabled);
}
void UiControllerAndroid::OnFeedbackButtonClicked() {
JNIEnv* env = AttachCurrentThread();
Java_AutofillAssistantUiController_showFeedback(
env, java_object_,
ConvertUTF8ToJavaString(env, ui_delegate_->GetDebugContext()));
}
void UiControllerAndroid::OnViewEvent(const EventHandler::EventKey& key) {
ui_delegate_->DispatchEvent(key);
}
void UiControllerAndroid::OnValueChanged(const std::string& identifier,
const ValueProto& value) {
ui_delegate_->GetUserModel()->SetValue(identifier, value);
}
void UiControllerAndroid::Shutdown(Metrics::DropOutReason reason) {
client_->Shutdown(reason);
}
void UiControllerAndroid::ShowSnackbar(base::TimeDelta delay,
const std::string& message,
base::OnceCallback<void()> action) {
if (delay.is_zero()) {
std::move(action).Run();
return;
}
JNIEnv* env = AttachCurrentThread();
auto jmodel = GetModel();
if (!Java_AssistantModel_getVisible(env, jmodel)) {
// If the UI is not visible, execute the action immediately.
std::move(action).Run();
return;
}
SetVisible(false);
snackbar_action_ = std::move(action);
Java_AutofillAssistantUiController_showSnackbar(
env, java_object_, static_cast<jint>(delay.InMilliseconds()),
ConvertUTF8ToJavaString(env, message));
}
void UiControllerAndroid::SnackbarResult(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
jboolean undo) {
base::OnceCallback<void()> action = std::move(snackbar_action_);
if (!action) {
NOTREACHED();
return;
}
if (undo) {
SetVisible(true);
return;
}
std::move(action).Run();
}
void UiControllerAndroid::DestroySelf() {
// Note: shutdown happens asynchronously (if at all), so calling code after
// |ShutdownIfNecessary| returns should be ok.
if (ui_delegate_)
ui_delegate_->ShutdownIfNecessary();
// Destroy self in separate task to avoid UaFs. Obviously, having this method
// in the first place is a terrible idea. We should refactor. The controller
// should always be in control. ui_controller should always hold a reference
// to ui_delegate, even when detached. ui_controller should not have a
// reference for client_android at all. It introduces a circular dependency,
// among other things.
//
// TODO(mcarlen): refactor lifecycle and deps of ui_controller.
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&ClientAndroid::DestroyUI, client_->GetWeakPtr()));
}
void UiControllerAndroid::SetVisible(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
jboolean visible) {
SetVisible(visible);
}
void UiControllerAndroid::SetVisible(bool visible) {
Java_AssistantModel_setVisible(AttachCurrentThread(), GetModel(), visible);
if (visible) {
// Recover possibly state changes missed by OnStateChanged()
SetupForState();
} else {
SetOverlayState(OverlayState::HIDDEN);
}
ui_delegate_->SetUiShown(visible);
}
void UiControllerAndroid::RestoreUi() {
if (ui_delegate_ == nullptr)
return;
OnStatusMessageChanged(ui_delegate_->GetStatusMessage());
OnBubbleMessageChanged(ui_delegate_->GetBubbleMessage());
auto step_progress_bar_configuration =
ui_delegate_->GetStepProgressBarConfiguration();
if (step_progress_bar_configuration.has_value()) {
OnStepProgressBarConfigurationChanged(*step_progress_bar_configuration);
if (step_progress_bar_configuration->use_step_progress_bar()) {
auto active_step = ui_delegate_->GetProgressActiveStep();
if (active_step.has_value()) {
OnProgressActiveStepChanged(*active_step);
}
OnProgressBarErrorStateChanged(ui_delegate_->GetProgressBarErrorState());
}
} else {
OnStepProgressBarConfigurationChanged(
ShowProgressBarProto::StepProgressBarConfiguration());
OnProgressChanged(ui_delegate_->GetProgress());
}
OnProgressVisibilityChanged(ui_delegate_->GetProgressVisible());
OnInfoBoxChanged(ui_delegate_->GetInfoBox());
OnDetailsChanged(ui_delegate_->GetDetails());
OnUserActionsChanged(ui_delegate_->GetUserActions());
OnCollectUserDataOptionsChanged(ui_delegate_->GetCollectUserDataOptions());
OnUserDataChanged(ui_delegate_->GetUserData(), UserData::FieldChange::ALL);
OnGenericUserInterfaceChanged(ui_delegate_->GetGenericUiProto());
std::vector<RectF> area;
ui_delegate_->GetTouchableArea(&area);
std::vector<RectF> restricted_area;
ui_delegate_->GetRestrictedArea(&restricted_area);
RectF visual_viewport;
ui_delegate_->GetVisualViewport(&visual_viewport);
OnTouchableAreaChanged(visual_viewport, area, restricted_area);
OnViewportModeChanged(ui_delegate_->GetViewportMode());
OnPeekModeChanged(ui_delegate_->GetPeekMode());
OnFormChanged(ui_delegate_->GetForm(), ui_delegate_->GetFormResult());
UiDelegate::OverlayColors colors;
ui_delegate_->GetOverlayColors(&colors);
OnOverlayColorsChanged(colors);
SetVisible(true);
Java_AutofillAssistantUiController_restoreBottomSheetState(
AttachCurrentThread(), java_object_,
ui_controller_android_utils::ToJavaBottomSheetState(
ui_delegate_->GetBottomSheetState()));
}
void UiControllerAndroid::OnTabSwitched(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
jint state,
jboolean activity_changed) {
if (ui_delegate_ == nullptr) {
return;
}
// TODO(b/167947210) Allow lite scripts to transition from CCT to regular
// scripts.
if (activity_changed && ui_delegate_->IsRunningLiteScript()) {
Shutdown(Metrics::DropOutReason::CUSTOM_TAB_CLOSED);
return;
}
ui_delegate_->SetBottomSheetState(
ui_controller_android_utils::ToNativeBottomSheetState(state));
ui_delegate_->SetTabSelected(false);
}
void UiControllerAndroid::OnTabSelected(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller) {
if (ui_delegate_ == nullptr) {
return;
}
if (!ui_delegate_->IsTabSelected()) {
RestoreUi();
ui_delegate_->SetTabSelected(true);
}
}
// Actions carousels related methods.
void UiControllerAndroid::UpdateActions(
const std::vector<UserAction>& user_actions) {
DCHECK(ui_delegate_);
JNIEnv* env = AttachCurrentThread();
bool has_close_or_cancel = false;
auto jchips = Java_AutofillAssistantUiController_createChipList(env);
auto jsticky_chips = Java_AutofillAssistantUiController_createChipList(env);
int user_action_count = static_cast<int>(user_actions.size());
for (int i = 0; i < user_action_count; i++) {
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;
case HIGHLIGHTED_ACTION:
jchip =
Java_AutofillAssistantUiController_createHighlightedActionButton(
env, java_object_, chip.icon,
ConvertUTF8ToJavaString(env, chip.text), i, !action.enabled(),
chip.sticky, chip.visible,
chip.is_content_description_set
? ConvertUTF8ToJavaString(env, chip.content_description)
: nullptr);
break;
case NORMAL_ACTION:
jchip = Java_AutofillAssistantUiController_createActionButton(
env, java_object_, chip.icon,
ConvertUTF8ToJavaString(env, chip.text), i, !action.enabled(),
chip.sticky, chip.visible,
chip.is_content_description_set
? ConvertUTF8ToJavaString(env, chip.content_description)
: nullptr);
break;
case FEEDBACK_ACTION:
// A "Send feedback" button which will show the feedback form before
// executing the action.
jchip = Java_AutofillAssistantUiController_createFeedbackButton(
env, java_object_, chip.icon,
ConvertUTF8ToJavaString(env, chip.text), i, !action.enabled(),
chip.sticky, chip.visible,
chip.is_content_description_set
? ConvertUTF8ToJavaString(env, chip.content_description)
: nullptr);
break;
case CANCEL_ACTION:
// A Cancel button sneaks in an UNDO snackbar before executing the
// action, while a close button behaves like a normal button.
jchip = Java_AutofillAssistantUiController_createCancelButton(
env, java_object_, chip.icon,
ConvertUTF8ToJavaString(env, chip.text), i, !action.enabled(),
chip.sticky, chip.visible,
chip.is_content_description_set
? ConvertUTF8ToJavaString(env, chip.content_description)
: nullptr);
has_close_or_cancel = true;
break;
case CLOSE_ACTION:
jchip = Java_AutofillAssistantUiController_createActionButton(
env, java_object_, chip.icon,
ConvertUTF8ToJavaString(env, chip.text), i, !action.enabled(),
chip.sticky, chip.visible,
chip.is_content_description_set
? ConvertUTF8ToJavaString(env, chip.content_description)
: nullptr);
has_close_or_cancel = true;
break;
case DONE_ACTION:
jchip =
Java_AutofillAssistantUiController_createHighlightedActionButton(
env, java_object_, chip.icon,
ConvertUTF8ToJavaString(env, chip.text), i, !action.enabled(),
chip.sticky, chip.visible,
chip.is_content_description_set
? ConvertUTF8ToJavaString(env, chip.content_description)
: nullptr);
has_close_or_cancel = true;
break;
}
if (jchip) {
Java_AutofillAssistantUiController_appendChipToList(env, jchips, jchip);
if (chip.sticky) {
Java_AutofillAssistantUiController_appendChipToList(env, jsticky_chips,
jchip);
}
}
}
if (!has_close_or_cancel) {
base::android::ScopedJavaLocalRef<jobject> jcancel_chip;
if (ui_delegate_->GetState() == AutofillAssistantState::STOPPED) {
jcancel_chip = Java_AutofillAssistantUiController_createCloseButton(
env, java_object_, ICON_CLEAR, ConvertUTF8ToJavaString(env, ""),
/* disabled= */ false, /* sticky= */ true, /* visible=*/true,
/* content_description= */ nullptr);
} else if (ui_delegate_->GetState() != AutofillAssistantState::INACTIVE) {
jcancel_chip = Java_AutofillAssistantUiController_createCancelButton(
env, java_object_, ICON_CLEAR, ConvertUTF8ToJavaString(env, ""), -1,
/* disabled= */ false, /* sticky= */ true, /* visible=*/true,
/* content_description= */ nullptr);
}
if (jcancel_chip) {
Java_AutofillAssistantUiController_appendChipToList(env, jchips,
jcancel_chip);
Java_AutofillAssistantUiController_appendChipToList(env, jsticky_chips,
jcancel_chip);
}
}
Java_AutofillAssistantUiController_setActions(env, java_object_, jchips);
header_model_->SetChips(jsticky_chips);
}
void UiControllerAndroid::OnUserActionsChanged(
const std::vector<UserAction>& actions) {
UpdateActions(actions);
}
void UiControllerAndroid::OnUserActionSelected(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
jint index) {
if (ui_delegate_)
ui_delegate_->PerformUserAction(index);
}
void UiControllerAndroid::OnCancelButtonClicked(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
jint index) {
// If the keyboard is currently shown, clicking the cancel button should
// hide the keyboard rather than close autofill assistant, because the cancel
// chip will be displayed right above the keyboard.
if (Java_AutofillAssistantUiController_isKeyboardShown(env, java_object_)) {
Java_AutofillAssistantUiController_hideKeyboard(env, java_object_);
return;
}
CloseOrCancel(index, TriggerContext::CreateEmpty(),
Metrics::DropOutReason::SHEET_CLOSED);
}
void UiControllerAndroid::OnCloseButtonClicked(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller) {
DestroySelf();
}
void UiControllerAndroid::OnFeedbackButtonClicked(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
jint index) {
// Show the feedback form then directly run the associated action.
// Unfortunately there is no way to associate a callback to run after the user
// actually sent (or close) the form, so we have to continue directly after
// showing it. It should be good enough, given that in most use cases we will
// directly stop.
Java_AutofillAssistantUiController_showFeedback(
env, java_object_,
ConvertUTF8ToJavaString(env, ui_delegate_->GetDebugContext()));
OnUserActionSelected(env, jcaller, index);
}
void UiControllerAndroid::OnKeyboardVisibilityChanged(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
jboolean visible) {
if (ui_delegate_)
ui_delegate_->OnKeyboardVisibilityChanged(visible);
}
bool UiControllerAndroid::OnBackButtonClicked() {
// If the keyboard is currently shown, clicking the back button should
// hide the keyboard rather than close autofill assistant.
if (Java_AutofillAssistantUiController_isKeyboardShown(AttachCurrentThread(),
java_object_)) {
Java_AutofillAssistantUiController_hideKeyboard(AttachCurrentThread(),
java_object_);
return true;
}
// For BROWSE state the back button should react in its default way.
if (ui_delegate_ != nullptr &&
(ui_delegate_->GetState() == AutofillAssistantState::BROWSE)) {
return false;
}
if (ui_delegate_ == nullptr ||
ui_delegate_->GetState() == AutofillAssistantState::STOPPED ||
ui_delegate_->IsRunningLiteScript()) {
if (client_->GetWebContents() != nullptr &&
client_->GetWebContents()->GetController().CanGoBack()) {
client_->GetWebContents()->GetController().GoBack();
}
// Lite scripts should not shut down here. The navigation will be handled
// by the lite script coordinator.
if (!ui_delegate_ || !ui_delegate_->IsRunningLiteScript()) {
Shutdown(Metrics::DropOutReason::BACK_BUTTON_CLICKED);
}
return true;
}
// ui_delegate_ must never be nullptr here!
auto back_button_settings =
ui_delegate_->GetClientSettings().back_button_settings;
if (back_button_settings.has_value()) {
ui_delegate_->OnStop(back_button_settings->message(),
back_button_settings->undo_label());
} else {
CloseOrCancel(-1, TriggerContext::CreateEmpty(),
Metrics::DropOutReason::BACK_BUTTON_CLICKED);
}
return true;
}
void UiControllerAndroid::OnBottomSheetClosedWithSwipe() {
if (ui_delegate_->IsTabSelected() && ui_delegate_->IsRunningLiteScript()) {
Shutdown(Metrics::DropOutReason::SHEET_CLOSED);
}
}
void UiControllerAndroid::CloseOrCancel(
int action_index,
std::unique_ptr<TriggerContext> trigger_context,
Metrics::DropOutReason dropout_reason) {
// Close immediately.
if (!ui_delegate_ ||
ui_delegate_->GetState() == AutofillAssistantState::STOPPED) {
DestroySelf();
return;
}
// Close, with an action.
const std::vector<UserAction>& user_actions = ui_delegate_->GetUserActions();
if (action_index >= 0 &&
static_cast<size_t>(action_index) < user_actions.size() &&
user_actions[action_index].chip().type == CLOSE_ACTION &&
ui_delegate_->PerformUserActionWithContext(action_index,
std::move(trigger_context))) {
return;
}
// Cancel, with a snackbar to allow UNDO.
ShowSnackbar(ui_delegate_->GetClientSettings().cancel_delay,
l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_STOPPED),
base::BindOnce(&UiControllerAndroid::OnCancel,
weak_ptr_factory_.GetWeakPtr(), action_index,
std::move(trigger_context), dropout_reason));
}
void UiControllerAndroid::OnCancel(
int action_index,
std::unique_ptr<TriggerContext> trigger_context,
Metrics::DropOutReason dropout_reason) {
if (action_index == -1 || !ui_delegate_ ||
!ui_delegate_->PerformUserActionWithContext(action_index,
std::move(trigger_context))) {
Shutdown(dropout_reason);
}
}
// Overlay related methods.
base::android::ScopedJavaLocalRef<jobject>
UiControllerAndroid::GetOverlayModel() {
return Java_AssistantModel_getOverlayModel(AttachCurrentThread(), GetModel());
}
void UiControllerAndroid::SetOverlayState(OverlayState state) {
desired_overlay_state_ = state;
// Ensure that we don't set partial state if the touchable area is empty. This
// is important because a partial overlay will be hidden if TalkBack is
// enabled, and we want to completely prevent TalkBack from accessing the web
// page if there is no touchable area.
if (state == OverlayState::PARTIAL) {
std::vector<RectF> area;
ui_delegate_->GetTouchableArea(&area);
if (area.empty()) {
state = OverlayState::FULL;
}
}
overlay_state_ = state;
if (ui_delegate_ && ui_delegate_->ShouldShowOverlay()) {
ApplyOverlayState(state);
}
}
void UiControllerAndroid::ApplyOverlayState(OverlayState state) {
Java_AssistantOverlayModel_setState(AttachCurrentThread(), GetOverlayModel(),
state);
Java_AssistantModel_setAllowTalkbackOnWebsite(
AttachCurrentThread(), GetModel(), state != OverlayState::FULL);
}
void UiControllerAndroid::OnShouldShowOverlayChanged(bool should_show) {
if (should_show) {
ApplyOverlayState(overlay_state_);
} else {
ApplyOverlayState(OverlayState::HIDDEN);
}
}
void UiControllerAndroid::OnTouchableAreaChanged(
const RectF& visual_viewport,
const std::vector<RectF>& touchable_areas,
const std::vector<RectF>& restricted_areas) {
if (!touchable_areas.empty() &&
desired_overlay_state_ == OverlayState::PARTIAL) {
SetOverlayState(OverlayState::PARTIAL);
}
JNIEnv* env = AttachCurrentThread();
Java_AssistantOverlayModel_setVisualViewport(
env, GetOverlayModel(), visual_viewport.left, visual_viewport.top,
visual_viewport.right, visual_viewport.bottom);
Java_AssistantOverlayModel_setTouchableArea(
env, GetOverlayModel(),
base::android::ToJavaFloatArray(env, ToFloatVector(touchable_areas)));
Java_AssistantOverlayModel_setRestrictedArea(
AttachCurrentThread(), GetOverlayModel(),
base::android::ToJavaFloatArray(env, ToFloatVector(restricted_areas)));
}
void UiControllerAndroid::OnUnexpectedTaps() {
if (!ui_delegate_) {
Shutdown(Metrics::DropOutReason::OVERLAY_STOP);
return;
}
ShowSnackbar(ui_delegate_->GetClientSettings().tap_shutdown_delay,
l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_MAYBE_GIVE_UP),
base::BindOnce(&UiControllerAndroid::Shutdown,
weak_ptr_factory_.GetWeakPtr(),
Metrics::DropOutReason::OVERLAY_STOP));
}
void UiControllerAndroid::OnUserInteractionInsideTouchableArea() {
if (ui_delegate_)
ui_delegate_->OnUserInteractionInsideTouchableArea();
}
// Other methods.
void UiControllerAndroid::CloseCustomTab() {
Java_AutofillAssistantUiController_scheduleCloseCustomTab(
AttachCurrentThread(), java_object_);
}
// Collect user data related methods.
base::android::ScopedJavaLocalRef<jobject>
UiControllerAndroid::GetCollectUserDataModel() {
return Java_AssistantModel_getCollectUserDataModel(AttachCurrentThread(),
GetModel());
}
void UiControllerAndroid::OnShippingAddressChanged(
std::unique_ptr<autofill::AutofillProfile> address) {
ui_delegate_->SetShippingAddress(std::move(address));
}
void UiControllerAndroid::OnContactInfoChanged(
std::unique_ptr<autofill::AutofillProfile> profile) {
ui_delegate_->SetContactInfo(std::move(profile));
}
void UiControllerAndroid::OnCreditCardChanged(
std::unique_ptr<autofill::CreditCard> card,
std::unique_ptr<autofill::AutofillProfile> billing_profile) {
ui_delegate_->SetCreditCard(std::move(card), std::move(billing_profile));
}
void UiControllerAndroid::OnTermsAndConditionsChanged(
TermsAndConditionsState state) {
ui_delegate_->SetTermsAndConditions(state);
}
void UiControllerAndroid::OnLoginChoiceChanged(std::string identifier) {
ui_delegate_->SetLoginOption(identifier);
}
void UiControllerAndroid::OnTextLinkClicked(int link) {
ui_delegate_->OnTextLinkClicked(link);
}
void UiControllerAndroid::OnFormActionLinkClicked(int link) {
ui_delegate_->OnFormActionLinkClicked(link);
}
void UiControllerAndroid::OnDateTimeRangeStartDateChanged(int year,
int month,
int day) {
auto date = base::make_optional<DateProto>();
date->set_year(year);
date->set_month(month);
date->set_day(day);
ui_delegate_->SetDateTimeRangeStartDate(date);
}
void UiControllerAndroid::OnDateTimeRangeStartDateCleared() {
ui_delegate_->SetDateTimeRangeStartDate(base::nullopt);
}
void UiControllerAndroid::OnDateTimeRangeStartTimeSlotChanged(int index) {
ui_delegate_->SetDateTimeRangeStartTimeSlot(base::make_optional<int>(index));
}
void UiControllerAndroid::OnDateTimeRangeStartTimeSlotCleared() {
ui_delegate_->SetDateTimeRangeStartTimeSlot(base::nullopt);
}
void UiControllerAndroid::OnDateTimeRangeEndDateChanged(int year,
int month,
int day) {
auto date = base::make_optional<DateProto>();
date->set_year(year);
date->set_month(month);
date->set_day(day);
ui_delegate_->SetDateTimeRangeEndDate(date);
}
void UiControllerAndroid::OnDateTimeRangeEndDateCleared() {
ui_delegate_->SetDateTimeRangeEndDate(base::nullopt);
}
void UiControllerAndroid::OnDateTimeRangeEndTimeSlotChanged(int index) {
ui_delegate_->SetDateTimeRangeEndTimeSlot(base::make_optional<int>(index));
}
void UiControllerAndroid::OnDateTimeRangeEndTimeSlotCleared() {
ui_delegate_->SetDateTimeRangeEndTimeSlot(base::nullopt);
}
void UiControllerAndroid::OnKeyValueChanged(const std::string& key,
const ValueProto& value) {
ui_delegate_->SetAdditionalValue(key, value);
}
void UiControllerAndroid::OnTextFocusLost() {
// We set a delay to avoid having the keyboard flickering when the focus goes
// from one text field to another
content::GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE,
base::BindOnce(&UiControllerAndroid::HideKeyboardIfFocusNotOnText,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(50));
}
bool UiControllerAndroid::IsContactComplete(
autofill::AutofillProfile* contact) {
auto* options = ui_delegate_->GetCollectUserDataOptions();
if (options == nullptr) {
return false;
}
return IsCompleteContact(contact, *options);
}
bool UiControllerAndroid::IsShippingAddressComplete(
autofill::AutofillProfile* address) {
auto* options = ui_delegate_->GetCollectUserDataOptions();
if (options == nullptr) {
return false;
}
return IsCompleteShippingAddress(address, *options);
}
bool UiControllerAndroid::IsPaymentInstrumentComplete(
autofill::CreditCard* card,
autofill::AutofillProfile* address) {
auto* options = ui_delegate_->GetCollectUserDataOptions();
if (options == nullptr) {
return false;
}
return IsCompleteCreditCard(card, address, *options);
}
void UiControllerAndroid::HideKeyboardIfFocusNotOnText() {
Java_AutofillAssistantUiController_hideKeyboardIfFocusNotOnText(
AttachCurrentThread(), java_object_);
}
void UiControllerAndroid::OnCollectUserDataOptionsChanged(
const CollectUserDataOptions* collect_user_data_options) {
JNIEnv* env = AttachCurrentThread();
auto jmodel = GetCollectUserDataModel();
if (!collect_user_data_options) {
ResetGenericUiControllers();
Java_AssistantCollectUserDataModel_setVisible(env, jmodel, false);
return;
}
Java_AssistantCollectUserDataModel_setRequestName(
env, jmodel, collect_user_data_options->request_payer_name);
Java_AssistantCollectUserDataModel_setRequestEmail(
env, jmodel, collect_user_data_options->request_payer_email);
Java_AssistantCollectUserDataModel_setRequestPhone(
env, jmodel, collect_user_data_options->request_payer_phone);
std::vector<int> contact_summary_fields;
for (const auto& field : collect_user_data_options->contact_summary_fields) {
contact_summary_fields.emplace_back((int)field);
}
Java_AssistantCollectUserDataModel_setContactSummaryDescriptionOptions(
env, jmodel,
Java_AssistantCollectUserDataModel_createContactDescriptionOptions(
env, base::android::ToJavaIntArray(env, contact_summary_fields),
collect_user_data_options->contact_summary_max_lines));
std::vector<int> contact_full_fields;
for (const auto& field : collect_user_data_options->contact_full_fields) {
contact_full_fields.emplace_back((int)field);
}
Java_AssistantCollectUserDataModel_setContactFullDescriptionOptions(
env, jmodel,
Java_AssistantCollectUserDataModel_createContactDescriptionOptions(
env, base::android::ToJavaIntArray(env, contact_full_fields),
collect_user_data_options->contact_full_max_lines));
Java_AssistantCollectUserDataModel_setRequestShippingAddress(
env, jmodel, collect_user_data_options->request_shipping);
Java_AssistantCollectUserDataModel_setRequestPayment(
env, jmodel, collect_user_data_options->request_payment_method);
Java_AssistantCollectUserDataModel_setRequestLoginChoice(
env, jmodel, collect_user_data_options->request_login_choice);
Java_AssistantCollectUserDataModel_setLoginSectionTitle(
env, jmodel,
ConvertUTF8ToJavaString(env,
collect_user_data_options->login_section_title));
Java_AssistantCollectUserDataModel_setContactSectionTitle(
env, jmodel,
ConvertUTF8ToJavaString(
env, collect_user_data_options->contact_details_section_title));
Java_AssistantCollectUserDataModel_setShippingSectionTitle(
env, jmodel,
ConvertUTF8ToJavaString(
env, collect_user_data_options->shipping_address_section_title));
Java_AssistantCollectUserDataModel_setAcceptTermsAndConditionsText(
env, jmodel,
ConvertUTF8ToJavaString(
env, collect_user_data_options->accept_terms_and_conditions_text));
Java_AssistantCollectUserDataModel_setShowTermsAsCheckbox(
env, jmodel, collect_user_data_options->show_terms_as_checkbox);
Java_AssistantCollectUserDataModel_setRequireBillingPostalCode(
env, jmodel, collect_user_data_options->require_billing_postal_code);
Java_AssistantCollectUserDataModel_setBillingPostalCodeMissingText(
env, jmodel,
ConvertUTF8ToJavaString(
env, collect_user_data_options->billing_postal_code_missing_text));
Java_AssistantCollectUserDataModel_setCreditCardExpiredText(
env, jmodel,
ConvertUTF8ToJavaString(
env, collect_user_data_options->credit_card_expired_text));
Java_AssistantCollectUserDataModel_setSupportedBasicCardNetworks(
env, jmodel,
base::android::ToJavaArrayOfStrings(
env, collect_user_data_options->supported_basic_card_networks));
if (collect_user_data_options->request_login_choice) {
auto jlist = CreateJavaLoginChoiceList(
env, collect_user_data_options->login_choices);
Java_AssistantCollectUserDataModel_setLoginChoices(env, jmodel, jlist);
}
Java_AssistantCollectUserDataModel_setRequestDateRange(
env, jmodel, collect_user_data_options->request_date_time_range);
if (collect_user_data_options->request_date_time_range) {
auto jmin_date = CreateJavaDate(
env, collect_user_data_options->date_time_range.min_date());
auto jmax_date = CreateJavaDate(
env, collect_user_data_options->date_time_range.max_date());
std::vector<std::string> time_slots;
for (const auto& slot :
collect_user_data_options->date_time_range.time_slots()) {
time_slots.emplace_back(slot.label());
}
auto jtime_slots = base::android::ToJavaArrayOfStrings(env, time_slots);
Java_AssistantCollectUserDataModel_setDateTimeRangeStartOptions(
env, jmodel, jmin_date, jmax_date, jtime_slots);
Java_AssistantCollectUserDataModel_setDateTimeRangeEndOptions(
env, jmodel, jmin_date, jmax_date, jtime_slots);
Java_AssistantCollectUserDataModel_setDateTimeRangeStartDateLabel(
env, jmodel,
ConvertUTF8ToJavaString(
env,
collect_user_data_options->date_time_range.start_date_label()));
Java_AssistantCollectUserDataModel_setDateTimeRangeStartTimeLabel(
env, jmodel,
ConvertUTF8ToJavaString(
env,
collect_user_data_options->date_time_range.start_time_label()));
Java_AssistantCollectUserDataModel_setDateTimeRangeEndDateLabel(
env, jmodel,
ConvertUTF8ToJavaString(
env, collect_user_data_options->date_time_range.end_date_label()));
Java_AssistantCollectUserDataModel_setDateTimeRangeEndTimeLabel(
env, jmodel,
ConvertUTF8ToJavaString(
env, collect_user_data_options->date_time_range.end_time_label()));
Java_AssistantCollectUserDataModel_setDateTimeRangeDateNotSetErrorMessage(
env, jmodel,
ConvertUTF8ToJavaString(
env,
collect_user_data_options->date_time_range.date_not_set_error()));
Java_AssistantCollectUserDataModel_setDateTimeRangeTimeNotSetErrorMessage(
env, jmodel,
ConvertUTF8ToJavaString(
env,
collect_user_data_options->date_time_range.time_not_set_error()));
}
Java_AssistantCollectUserDataModel_setTermsRequireReviewText(
env, jmodel,
ConvertUTF8ToJavaString(
env, collect_user_data_options->terms_require_review_text));
Java_AssistantCollectUserDataModel_setInfoSectionText(
env, jmodel,
ConvertUTF8ToJavaString(env,
collect_user_data_options->info_section_text),
collect_user_data_options->info_section_text_center);
Java_AssistantCollectUserDataModel_setPrivacyNoticeText(
env, jmodel,
ConvertUTF8ToJavaString(env,
collect_user_data_options->privacy_notice_text));
Java_AssistantCollectUserDataModel_setPrependedSections(
env, jmodel,
CreateJavaAdditionalSections(
env, collect_user_data_options->additional_prepended_sections));
Java_AssistantCollectUserDataModel_setAppendedSections(
env, jmodel,
CreateJavaAdditionalSections(
env, collect_user_data_options->additional_appended_sections));
if (collect_user_data_options->generic_user_interface_prepended.has_value()) {
collect_user_data_prepended_generic_ui_controller_ =
CreateGenericUiControllerForProto(
*collect_user_data_options->generic_user_interface_prepended);
Java_AssistantCollectUserDataModel_setGenericUserInterfacePrepended(
env, jmodel,
collect_user_data_prepended_generic_ui_controller_ != nullptr
? collect_user_data_prepended_generic_ui_controller_->GetRootView()
: nullptr);
}
if (collect_user_data_options->generic_user_interface_appended.has_value()) {
collect_user_data_appended_generic_ui_controller_ =
CreateGenericUiControllerForProto(
*collect_user_data_options->generic_user_interface_appended);
Java_AssistantCollectUserDataModel_setGenericUserInterfaceAppended(
env, jmodel,
collect_user_data_appended_generic_ui_controller_ != nullptr
? collect_user_data_appended_generic_ui_controller_->GetRootView()
: nullptr);
}
Java_AssistantCollectUserDataModel_setVisible(env, jmodel, true);
}
void UiControllerAndroid::OnUserDataChanged(
const UserData* state,
UserData::FieldChange field_change) {
JNIEnv* env = AttachCurrentThread();
auto jmodel = GetCollectUserDataModel();
if (!state) {
return;
}
DCHECK(ui_delegate_ != nullptr);
DCHECK(client_->GetWebContents() != nullptr);
const CollectUserDataOptions* collect_user_data_options =
ui_delegate_->GetCollectUserDataOptions();
if (collect_user_data_options == nullptr) {
// If there are no options, there currently is no active
// CollectUserDataAction, the UI is not shown and does not need to be
// updated.
return;
}
auto jcontext =
Java_AutofillAssistantUiController_getContext(env, java_object_);
auto web_contents = client_->GetWebContents()->GetJavaWebContents();
if (field_change == UserData::FieldChange::ALL ||
field_change == UserData::FieldChange::TERMS_AND_CONDITIONS) {
Java_AssistantCollectUserDataModel_setTermsStatus(
env, jmodel, state->terms_and_conditions_);
}
if (field_change == UserData::FieldChange::ALL ||
field_change == UserData::FieldChange::AVAILABLE_PROFILES) {
// Contact profiles.
auto jcontactlist =
Java_AssistantCollectUserDataModel_createAutofillContactList(env);
auto contact_indices = SortContactsByCompleteness(
*collect_user_data_options, state->available_profiles_);
for (int index : contact_indices) {
auto jcontact = Java_AssistantCollectUserDataModel_createAutofillContact(
env, jcontext,
autofill::PersonalDataManagerAndroid::CreateJavaProfileFromNative(
env, *state->available_profiles_[index]),
collect_user_data_options->request_payer_name,
collect_user_data_options->request_payer_phone,
collect_user_data_options->request_payer_email);
if (jcontact) {
Java_AssistantCollectUserDataModel_addAutofillContact(env, jcontactlist,
jcontact);
}
}
Java_AssistantCollectUserDataModel_setAvailableContacts(env, jmodel,
jcontactlist);
// Ignore changes to FieldChange::CONTACT_PROFILE, this is already coming
// from the view.
const autofill::AutofillProfile* contact_profile = state->selected_address(
collect_user_data_options->contact_details_name);
Java_AssistantCollectUserDataModel_setSelectedContactDetails(
env, jmodel,
contact_profile == nullptr
? nullptr
: Java_AssistantCollectUserDataModel_createAutofillContact(
env, jcontext,
autofill::PersonalDataManagerAndroid::
CreateJavaProfileFromNative(env, *contact_profile),
collect_user_data_options->request_payer_name,
collect_user_data_options->request_payer_phone,
collect_user_data_options->request_payer_email));
// Billing addresses profiles.
auto jbillinglist =
Java_AssistantCollectUserDataModel_createAutofillAddressList(env);
for (const auto& profile : state->available_profiles_) {
auto jaddress = Java_AssistantCollectUserDataModel_createAutofillAddress(
env, jcontext,
autofill::PersonalDataManagerAndroid::CreateJavaProfileFromNative(
env, *profile));
if (jaddress) {
Java_AssistantCollectUserDataModel_addAutofillAddress(env, jbillinglist,
jaddress);
}
}
Java_AssistantCollectUserDataModel_setAvailableBillingAddresses(
env, jmodel, jbillinglist);
// Address profiles.
auto jshippinglist =
Java_AssistantCollectUserDataModel_createAutofillAddressList(env);
auto address_indices = SortAddressesByCompleteness(
*collect_user_data_options, state->available_profiles_);
for (int index : address_indices) {
auto jaddress = Java_AssistantCollectUserDataModel_createAutofillAddress(
env, jcontext,
autofill::PersonalDataManagerAndroid::CreateJavaProfileFromNative(
env, *state->available_profiles_[index]));
if (jaddress) {
Java_AssistantCollectUserDataModel_addAutofillAddress(
env, jshippinglist, jaddress);
}
}
Java_AssistantCollectUserDataModel_setAvailableShippingAddresses(
env, jmodel, jshippinglist);
// Ignore changes to FieldChange::SHIPPING_ADDRESS, this is already coming
// from the view.
const autofill::AutofillProfile* shipping_address = state->selected_address(
collect_user_data_options->shipping_address_name);
Java_AssistantCollectUserDataModel_setSelectedShippingAddress(
env, jmodel,
shipping_address == nullptr
? nullptr
: Java_AssistantCollectUserDataModel_createAutofillAddress(
env, jcontext,
autofill::PersonalDataManagerAndroid::
CreateJavaProfileFromNative(env, *shipping_address)));
}
if (field_change == UserData::FieldChange::ALL ||
field_change == UserData::FieldChange::AVAILABLE_PAYMENT_INSTRUMENTS) {
auto jlist =
Java_AssistantCollectUserDataModel_createAutofillPaymentInstrumentList(
env);
auto sorted_payment_instrument_indices =
SortPaymentInstrumentsByCompleteness(
*collect_user_data_options, state->available_payment_instruments_);
for (int index : sorted_payment_instrument_indices) {
const auto& instrument = state->available_payment_instruments_[index];
Java_AssistantCollectUserDataModel_addAutofillPaymentInstrument(
env, jlist, web_contents,
instrument->card == nullptr
? nullptr
: autofill::PersonalDataManagerAndroid::
CreateJavaCreditCardFromNative(env, *(instrument->card)),
instrument->billing_address == nullptr
? nullptr
: autofill::PersonalDataManagerAndroid::
CreateJavaProfileFromNative(
env, *(instrument->billing_address)));
}
Java_AssistantCollectUserDataModel_setAvailablePaymentInstruments(
env, jmodel, jlist);
// Ignore changes to FieldChange::CARD, this is already coming from the
// view.
autofill::CreditCard* card = state->selected_card_.get();
const autofill::AutofillProfile* billing_address = state->selected_address(
collect_user_data_options->billing_address_name);
Java_AssistantCollectUserDataModel_setSelectedPaymentInstrument(
env, jmodel, web_contents,
card == nullptr ? nullptr
: autofill::PersonalDataManagerAndroid::
CreateJavaCreditCardFromNative(env, *card),
billing_address == nullptr
? nullptr
: autofill::PersonalDataManagerAndroid::CreateJavaProfileFromNative(
env, *billing_address));
}
if (field_change == UserData::FieldChange::ALL ||
field_change == UserData::FieldChange::DATE_TIME_RANGE_START) {
if (state->date_time_range_start_date_.has_value()) {
Java_AssistantCollectUserDataModel_setDateTimeRangeStartDate(
env, jmodel,
CreateJavaDate(env, *state->date_time_range_start_date_));
} else {
Java_AssistantCollectUserDataModel_clearDateTimeRangeStartDate(env,
jmodel);
}
if (state->date_time_range_start_timeslot_.has_value()) {
Java_AssistantCollectUserDataModel_setDateTimeRangeStartTimeSlot(
env, jmodel, *state->date_time_range_start_timeslot_);
} else {
Java_AssistantCollectUserDataModel_clearDateTimeRangeStartTimeSlot(
env, jmodel);
}
}
if (field_change == UserData::FieldChange::ALL ||
field_change == UserData::FieldChange::DATE_TIME_RANGE_END) {
if (state->date_time_range_end_date_.has_value()) {
Java_AssistantCollectUserDataModel_setDateTimeRangeEndDate(
env, jmodel, CreateJavaDate(env, *state->date_time_range_end_date_));
} else {
Java_AssistantCollectUserDataModel_clearDateTimeRangeEndDate(env, jmodel);
}
if (state->date_time_range_end_timeslot_.has_value()) {
Java_AssistantCollectUserDataModel_setDateTimeRangeEndTimeSlot(
env, jmodel, *state->date_time_range_end_timeslot_);
} else {
Java_AssistantCollectUserDataModel_clearDateTimeRangeEndTimeSlot(env,
jmodel);
}
}
// TODO(crbug.com/806868): Add |setSelectedLogin|.
}
// FormProto related methods.
base::android::ScopedJavaLocalRef<jobject> UiControllerAndroid::GetFormModel() {
return Java_AssistantModel_getFormModel(AttachCurrentThread(), GetModel());
}
void UiControllerAndroid::OnFormChanged(const FormProto* form,
const FormProto::Result* result) {
JNIEnv* env = AttachCurrentThread();
if (!form) {
Java_AssistantFormModel_clearInputs(env, GetFormModel());
return;
}
auto jinput_list = Java_AssistantFormModel_createInputList(env);
for (int i = 0; i < form->inputs_size(); i++) {
const FormInputProto input = form->inputs(i);
switch (input.input_type_case()) {
case FormInputProto::InputTypeCase::kCounter: {
CounterInputProto counter_input = input.counter();
auto jcounters = Java_AssistantFormInput_createCounterList(env);
for (int j = 0; j < counter_input.counters_size(); ++j) {
const CounterInputProto::Counter& counter = counter_input.counters(j);
std::vector<int> allowed_values;
for (int value : counter.allowed_values()) {
allowed_values.push_back(value);
}
auto result_value = GetPreviousFormCounterResult(result, i, j);
Java_AssistantFormInput_addCounter(
env, jcounters,
Java_AssistantFormInput_createCounter(
env, ConvertUTF8ToJavaString(env, counter.label()),
ConvertUTF8ToJavaString(env, counter.description_line_1()),
ConvertUTF8ToJavaString(env, counter.description_line_2()),
result_value.has_value() ? result_value.value()
: counter.initial_value(),
counter.min_value(), counter.max_value(),
base::android::ToJavaIntArray(env, allowed_values)));
}
Java_AssistantFormModel_addInput(
env, jinput_list,
Java_AssistantFormInput_createCounterInput(
env, i, ConvertUTF8ToJavaString(env, counter_input.label()),
ConvertUTF8ToJavaString(env, counter_input.expand_text()),
ConvertUTF8ToJavaString(env, counter_input.minimize_text()),
jcounters, counter_input.minimized_count(),
counter_input.min_counters_sum(),
counter_input.max_counters_sum(),
form_delegate_.GetJavaObject()));
break;
}
case FormInputProto::InputTypeCase::kSelection: {
SelectionInputProto selection_input = input.selection();
auto jchoices = Java_AssistantFormInput_createChoiceList(env);
for (int j = 0; j < selection_input.choices_size(); ++j) {
const SelectionInputProto::Choice& choice =
selection_input.choices(j);
auto result_value = GetPreviousFormSelectionResult(result, i, j);
Java_AssistantFormInput_addChoice(
env, jchoices,
Java_AssistantFormInput_createChoice(
env, ConvertUTF8ToJavaString(env, choice.label()),
ConvertUTF8ToJavaString(env, choice.description_line_1()),
ConvertUTF8ToJavaString(env, choice.description_line_2()),
result_value.has_value() ? result_value.value()
: choice.selected()));
}
Java_AssistantFormModel_addInput(
env, jinput_list,
Java_AssistantFormInput_createSelectionInput(
env, i, ConvertUTF8ToJavaString(env, selection_input.label()),
jchoices, selection_input.allow_multiple(),
form_delegate_.GetJavaObject()));
break;
}
case FormInputProto::InputTypeCase::INPUT_TYPE_NOT_SET:
NOTREACHED();
break;
// Intentionally no default case to make compilation fail if a new value
// was added to the enum but not to this list.
}
}
Java_AssistantFormModel_setInputs(env, GetFormModel(), jinput_list);
if (form->has_info_label()) {
Java_AssistantFormModel_setInfoLabel(
env, GetFormModel(), ConvertUTF8ToJavaString(env, form->info_label()));
} else {
Java_AssistantFormModel_clearInfoLabel(env, GetFormModel());
}
if (form->has_info_popup()) {
Java_AssistantFormModel_setInfoPopup(
env, GetFormModel(),
ui_controller_android_utils::CreateJavaInfoPopup(env,
form->info_popup()));
} else {
Java_AssistantFormModel_clearInfoPopup(env, GetFormModel());
}
}
void UiControllerAndroid::OnClientSettingsChanged(
const ClientSettings& settings) {
JNIEnv* env = AttachCurrentThread();
Java_AssistantOverlayModel_setTapTracking(
env, GetOverlayModel(), settings.tap_count,
settings.tap_tracking_duration.InMilliseconds());
if (settings.overlay_image.has_value()) {
auto jcontext =
Java_AutofillAssistantUiController_getContext(env, java_object_);
const auto& image = *(settings.overlay_image);
int image_size = ui_controller_android_utils::GetPixelSizeOrDefault(
env, jcontext, image.image_size(), 0);
int top_margin = ui_controller_android_utils::GetPixelSizeOrDefault(
env, jcontext, image.image_top_margin(), 0);
int bottom_margin = ui_controller_android_utils::GetPixelSizeOrDefault(
env, jcontext, image.image_bottom_margin(), 0);
int text_size = ui_controller_android_utils::GetPixelSizeOrDefault(
env, jcontext, image.text_size(), 0);
Java_AssistantOverlayModel_setOverlayImage(
env, GetOverlayModel(), jcontext,
ui_controller_android_utils::CreateJavaDrawable(
env, jcontext, image.image_drawable(),
ui_delegate_->GetUserModel()),
image_size, top_margin, bottom_margin,
ConvertUTF8ToJavaString(env, image.text()),
ui_controller_android_utils::GetJavaColor(env, image.text_color()),
text_size);
} else {
Java_AssistantOverlayModel_clearOverlayImage(env, GetOverlayModel());
}
if (settings.integration_test_settings.has_value()) {
header_model_->SetDisableAnimations(
settings.integration_test_settings->disable_header_animations());
Java_AutofillAssistantUiController_setDisableChipChangeAnimations(
env, java_object_,
settings.integration_test_settings
->disable_carousel_change_animations());
}
Java_AssistantModel_setTalkbackSheetSizeFraction(
env, GetModel(), settings.talkback_sheet_size_fraction);
}
void UiControllerAndroid::OnGenericUserInterfaceChanged(
const GenericUserInterfaceProto* generic_ui) {
// Try to inflate user interface from proto.
if (generic_ui != nullptr) {
generic_ui_controller_ = CreateGenericUiControllerForProto(*generic_ui);
ClientStatus status(generic_ui_controller_ ? ACTION_APPLIED
: INVALID_ACTION);
ui_delegate_->GetBasicInteractions()->NotifyViewInflationFinished(status);
} else {
generic_ui_controller_.reset();
}
// Set or clear generic UI.
Java_AssistantGenericUiModel_setView(
AttachCurrentThread(), GetGenericUiModel(),
generic_ui_controller_ != nullptr ? generic_ui_controller_->GetRootView()
: nullptr);
}
void UiControllerAndroid::OnCounterChanged(int input_index,
int counter_index,
int value) {
ui_delegate_->SetCounterValue(input_index, counter_index, value);
}
void UiControllerAndroid::OnChoiceSelectionChanged(int input_index,
int choice_index,
bool selected) {
ui_delegate_->SetChoiceSelected(input_index, choice_index, selected);
}
// Details related method.
base::android::ScopedJavaLocalRef<jobject>
UiControllerAndroid::GetDetailsModel() {
return Java_AssistantModel_getDetailsModel(AttachCurrentThread(), GetModel());
}
void UiControllerAndroid::OnDetailsChanged(const Details* details) {
JNIEnv* env = AttachCurrentThread();
auto jmodel = GetDetailsModel();
if (!details) {
Java_AssistantDetailsModel_clearDetails(env, jmodel);
return;
}
auto opt_image_accessibility_hint = details->imageAccessibilityHint();
base::android::ScopedJavaLocalRef<jstring> jimage_accessibility_hint =
nullptr;
if (opt_image_accessibility_hint.has_value()) {
jimage_accessibility_hint =
ConvertUTF8ToJavaString(env, opt_image_accessibility_hint.value());
}
auto jdetails = Java_AssistantDetails_create(
env, ConvertUTF8ToJavaString(env, details->title()),
details->titleMaxLines(),
ConvertUTF8ToJavaString(env, details->imageUrl()),
jimage_accessibility_hint, details->imageAllowClickthrough(),
ConvertUTF8ToJavaString(env, details->imageDescription()),
ConvertUTF8ToJavaString(env, details->imagePositiveText()),
ConvertUTF8ToJavaString(env, details->imageNegativeText()),
ConvertUTF8ToJavaString(env, details->imageClickthroughUrl()),
details->showImagePlaceholder(),
ConvertUTF8ToJavaString(env, details->totalPriceLabel()),
ConvertUTF8ToJavaString(env, details->totalPrice()),
ConvertUTF8ToJavaString(env, details->descriptionLine1()),
ConvertUTF8ToJavaString(env, details->descriptionLine2()),
ConvertUTF8ToJavaString(env, details->descriptionLine3()),
ConvertUTF8ToJavaString(env, details->priceAttribution()),
details->userApprovalRequired(), details->highlightTitle(),
details->highlightLine1(), details->highlightLine2(),
details->highlightLine3(), details->animatePlaceholders());
Java_AssistantDetailsModel_setDetails(env, jmodel, jdetails);
}
// InfoBox related method.
base::android::ScopedJavaLocalRef<jobject>
UiControllerAndroid::GetInfoBoxModel() {
return Java_AssistantModel_getInfoBoxModel(AttachCurrentThread(), GetModel());
}
void UiControllerAndroid::OnInfoBoxChanged(const InfoBox* info_box) {
JNIEnv* env = AttachCurrentThread();
auto jmodel = GetInfoBoxModel();
if (!info_box) {
Java_AssistantInfoBoxModel_clearInfoBox(env, jmodel);
return;
}
const InfoBoxProto& proto = info_box->proto().info_box();
auto jinfo_box = Java_AssistantInfoBox_create(
env, ConvertUTF8ToJavaString(env, proto.image_path()),
ConvertUTF8ToJavaString(env, proto.explanation()));
Java_AssistantInfoBoxModel_setInfoBox(env, jmodel, jinfo_box);
}
void UiControllerAndroid::Stop(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
int jreason) {
client_->Shutdown(static_cast<Metrics::DropOutReason>(jreason));
}
void UiControllerAndroid::OnFatalError(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
const base::android::JavaParamRef<jstring>& jmessage,
int jreason) {
if (!ui_delegate_)
return;
ui_delegate_->OnFatalError(
base::android::ConvertJavaStringToUTF8(env, jmessage),
static_cast<Metrics::DropOutReason>(jreason));
}
void UiControllerAndroid::ResetGenericUiControllers() {
JNIEnv* env = AttachCurrentThread();
collect_user_data_prepended_generic_ui_controller_.reset();
collect_user_data_appended_generic_ui_controller_.reset();
generic_ui_controller_.reset();
auto jcollectuserdatamodel = GetCollectUserDataModel();
Java_AssistantCollectUserDataModel_setGenericUserInterfacePrepended(
env, jcollectuserdatamodel, nullptr);
Java_AssistantCollectUserDataModel_setGenericUserInterfaceAppended(
env, jcollectuserdatamodel, nullptr);
Java_AssistantGenericUiModel_setView(env, GetGenericUiModel(), nullptr);
}
std::unique_ptr<GenericUiRootControllerAndroid>
UiControllerAndroid::CreateGenericUiControllerForProto(
const GenericUserInterfaceProto& proto) {
JNIEnv* env = AttachCurrentThread();
auto jcontext =
Java_AutofillAssistantUiController_getContext(env, java_object_);
return GenericUiRootControllerAndroid::CreateFromProto(
proto, base::android::ScopedJavaGlobalRef<jobject>(jcontext),
generic_ui_delegate_.GetJavaObject(), ui_delegate_->GetEventHandler(),
ui_delegate_->GetUserModel(), ui_delegate_->GetBasicInteractions());
}
base::android::ScopedJavaLocalRef<jobject>
UiControllerAndroid::GetGenericUiModel() {
return Java_AssistantModel_getGenericUiModel(AttachCurrentThread(),
GetModel());
}
} // namespace autofill_assistant