blob: 19e93708da91df84eb607d6e660e3aa40aacbf09 [file] [log] [blame]
// Copyright 2019 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/interaction_handler_android.h"
#include <algorithm>
#include <vector>
#include "base/android/jni_string.h"
#include "base/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/android/autofill_assistant/generic_ui_interactions_android.h"
#include "chrome/browser/android/autofill_assistant/generic_ui_nested_controller_android.h"
#include "chrome/browser/android/autofill_assistant/view_handler_android.h"
#include "components/autofill_assistant/browser/basic_interactions.h"
#include "components/autofill_assistant/browser/generic_ui.pb.h"
#include "components/autofill_assistant/browser/generic_ui_replace_placeholders.h"
#include "components/autofill_assistant/browser/ui_delegate.h"
#include "components/autofill_assistant/browser/user_model.h"
#include "components/autofill_assistant/browser/value_util.h"
namespace autofill_assistant {
namespace {
// Runs |callbacks|. Early-terminates if a callback causes the action to end.
void RunCallbacks(
std::vector<InteractionHandlerAndroid::InteractionCallback> callbacks,
base::WeakPtr<InteractionHandlerAndroid> interaction_handler,
base::WeakPtr<UserModel> user_model,
base::WeakPtr<ViewHandlerAndroid> view_handler) {
if (!interaction_handler || !user_model || !view_handler) {
return;
}
for (const auto& callback : callbacks) {
callback.Run();
// A callback may have caused |interaction_handler| to go out of scope.
if (!interaction_handler) {
return;
}
}
}
void RunForEachLoop(
const ForEachProto& proto,
base::WeakPtr<InteractionHandlerAndroid> interaction_handler,
base::WeakPtr<UserModel> user_model,
base::WeakPtr<ViewHandlerAndroid> view_handler) {
if (!interaction_handler || !user_model || !view_handler) {
return;
}
auto loop_value = user_model->GetValue(proto.loop_value_model_identifier());
if (!loop_value.has_value()) {
VLOG(2) << "Error running ForEach loop: "
<< proto.loop_value_model_identifier() << " not found in model";
return;
}
for (int i = 0; i < GetValueSize(*loop_value); ++i) {
std::vector<InteractionHandlerAndroid::InteractionCallback> callbacks;
// Note: callback protos are copied and then modified. |proto| is unchanged.
for (auto callback_proto_copy : proto.callbacks()) {
ReplacePlaceholdersInCallback(
&callback_proto_copy,
{{proto.loop_counter(), base::NumberToString(i)}});
auto callback = interaction_handler->CreateInteractionCallbackFromProto(
callback_proto_copy);
if (!callback.has_value()) {
// Should never happen.
VLOG(1) << "Error creating ForEach interaction: failed to create "
"callback";
return;
}
callbacks.emplace_back(*callback);
}
RunCallbacks(callbacks, interaction_handler, user_model, view_handler);
}
}
} // namespace
InteractionHandlerAndroid::InteractionHandlerAndroid(
EventHandler* event_handler,
UserModel* user_model,
BasicInteractions* basic_interactions,
ViewHandlerAndroid* view_handler,
RadioButtonController* radio_button_controller,
base::android::ScopedJavaGlobalRef<jobject> jcontext,
base::android::ScopedJavaGlobalRef<jobject> jdelegate)
: event_handler_(event_handler),
user_model_(user_model),
basic_interactions_(basic_interactions),
view_handler_(view_handler),
radio_button_controller_(radio_button_controller),
jcontext_(jcontext),
jdelegate_(jdelegate) {}
InteractionHandlerAndroid::~InteractionHandlerAndroid() {
event_handler_->RemoveObserver(this);
}
base::WeakPtr<InteractionHandlerAndroid>
InteractionHandlerAndroid::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void InteractionHandlerAndroid::StartListening() {
is_listening_ = true;
event_handler_->AddObserver(this);
}
void InteractionHandlerAndroid::StopListening() {
event_handler_->RemoveObserver(this);
is_listening_ = false;
}
UserModel* InteractionHandlerAndroid::GetUserModel() const {
return user_model_;
}
BasicInteractions* InteractionHandlerAndroid::GetBasicInteractions() const {
return basic_interactions_;
}
bool InteractionHandlerAndroid::AddInteractionsFromProto(
const InteractionProto& proto) {
if (is_listening_) {
NOTREACHED() << "Interactions can not be added while listening to events!";
return false;
}
std::vector<InteractionHandlerAndroid::InteractionCallback> callbacks;
for (const auto& callback_proto : proto.callbacks()) {
auto callback = CreateInteractionCallbackFromProto(callback_proto);
if (!callback) {
VLOG(1) << "Invalid callback for interaction";
return false;
}
// Wrap callback in condition handler if necessary.
if (callback_proto.has_condition_model_identifier()) {
callback = base::Optional<InteractionCallback>(base::BindRepeating(
&android_interactions::RunConditionalCallback,
basic_interactions_->GetWeakPtr(),
callback_proto.condition_model_identifier(), *callback));
}
callbacks.push_back(std::move(*callback));
}
for (const auto& trigger_event : proto.trigger_event()) {
auto key = EventHandler::CreateEventKeyFromProto(trigger_event);
if (!key) {
VLOG(1) << "Invalid trigger event of type " << trigger_event.kind_case();
return false;
}
for (const auto& callback : callbacks) {
AddInteraction(*key, callback);
}
}
return true;
}
void InteractionHandlerAndroid::AddInteraction(
const EventHandler::EventKey& key,
const InteractionCallback& callback) {
interactions_[key].emplace_back(callback);
}
void InteractionHandlerAndroid::OnEvent(const EventHandler::EventKey& key) {
auto it = interactions_.find(key);
if (it != interactions_.end()) {
RunCallbacks(it->second, this->GetWeakPtr(), user_model_->GetWeakPtr(),
view_handler_->GetWeakPtr());
// Note: it is unsafe to call any code after running callbacks, because
// a callback may effectively delete *this.
}
}
base::Optional<InteractionHandlerAndroid::InteractionCallback>
InteractionHandlerAndroid::CreateInteractionCallbackFromProto(
const CallbackProto& proto) {
switch (proto.kind_case()) {
case CallbackProto::kSetValue:
if (!proto.set_value().has_value()) {
VLOG(1) << "Error creating SetValue interaction: value "
"not set";
return base::nullopt;
}
return base::Optional<InteractionCallback>(base::BindRepeating(
&android_interactions::SetValue, basic_interactions_->GetWeakPtr(),
proto.set_value()));
case CallbackProto::kShowInfoPopup: {
return base::Optional<InteractionCallback>(
base::BindRepeating(&android_interactions::ShowInfoPopup,
proto.show_info_popup().info_popup(), jcontext_));
}
case CallbackProto::kShowListPopup:
if (!proto.show_list_popup().has_item_names()) {
VLOG(1) << "Error creating ShowListPopup interaction: "
"item_names not set";
return base::nullopt;
}
if (proto.show_list_popup()
.selected_item_indices_model_identifier()
.empty()) {
VLOG(1) << "Error creating ShowListPopup interaction: "
"selected_item_indices_model_identifier not set";
return base::nullopt;
}
return base::Optional<InteractionCallback>(base::BindRepeating(
&android_interactions::ShowListPopup, user_model_->GetWeakPtr(),
proto.show_list_popup(), jcontext_, jdelegate_));
case CallbackProto::kComputeValue:
if (proto.compute_value().result_model_identifier().empty()) {
VLOG(1) << "Error creating ComputeValue interaction: "
"result_model_identifier empty";
return base::nullopt;
}
return base::Optional<InteractionCallback>(base::BindRepeating(
&android_interactions::ComputeValue,
basic_interactions_->GetWeakPtr(), proto.compute_value()));
case CallbackProto::kSetUserActions:
if (!proto.set_user_actions().has_user_actions()) {
VLOG(1) << "Error creating SetUserActions interaction: "
"user_actions not set";
return base::nullopt;
}
return base::Optional<InteractionCallback>(base::BindRepeating(
&android_interactions::SetUserActions,
basic_interactions_->GetWeakPtr(), proto.set_user_actions()));
case CallbackProto::kEndAction:
return base::Optional<InteractionCallback>(base::BindRepeating(
&android_interactions::EndAction, basic_interactions_->GetWeakPtr(),
proto.end_action()));
case CallbackProto::kShowCalendarPopup:
if (proto.show_calendar_popup().date_model_identifier().empty()) {
VLOG(1) << "Error creating ShowCalendarPopup interaction: "
"date_model_identifier not set";
return base::nullopt;
}
return base::Optional<InteractionCallback>(base::BindRepeating(
&android_interactions::ShowCalendarPopup, user_model_->GetWeakPtr(),
proto.show_calendar_popup(), jcontext_, jdelegate_));
case CallbackProto::kSetText:
if (!proto.set_text().has_text()) {
VLOG(1) << "Error creating SetText interaction: "
"text not set";
return base::nullopt;
}
if (proto.set_text().view_identifier().empty()) {
VLOG(1) << "Error creating SetText interaction: "
"view_identifier not set";
return base::nullopt;
}
return base::Optional<InteractionCallback>(base::BindRepeating(
&android_interactions::SetViewText, user_model_->GetWeakPtr(),
proto.set_text(), view_handler_, jdelegate_));
case CallbackProto::kToggleUserAction:
if (proto.toggle_user_action().user_actions_model_identifier().empty()) {
VLOG(1) << "Error creating ToggleUserAction interaction: "
"user_actions_model_identifier not set";
return base::nullopt;
}
if (proto.toggle_user_action().user_action_identifier().empty()) {
VLOG(1) << "Error creating ToggleUserAction interaction: "
"user_action_identifier not set";
return base::nullopt;
}
if (!proto.toggle_user_action().has_enabled()) {
VLOG(1) << "Error creating ToggleUserAction interaction: "
"enabled not set";
return base::nullopt;
}
return base::Optional<InteractionCallback>(base::BindRepeating(
&android_interactions::ToggleUserAction,
basic_interactions_->GetWeakPtr(), proto.toggle_user_action()));
case CallbackProto::kSetViewVisibility:
if (proto.set_view_visibility().view_identifier().empty()) {
VLOG(1) << "Error creating SetViewVisibility interaction: "
"view_identifier not set";
return base::nullopt;
}
if (!proto.set_view_visibility().has_visible()) {
VLOG(1) << "Error creating SetViewVisibility interaction: "
"visible not set";
return base::nullopt;
}
return base::Optional<InteractionCallback>(base::BindRepeating(
&android_interactions::SetViewVisibility, user_model_->GetWeakPtr(),
proto.set_view_visibility(), view_handler_));
case CallbackProto::kSetViewEnabled:
if (proto.set_view_enabled().view_identifier().empty()) {
VLOG(1) << "Error creating SetViewEnabled interaction: "
"view_identifier not set";
return base::nullopt;
}
if (!proto.set_view_enabled().has_enabled()) {
VLOG(1) << "Error creating SetViewEnabled interaction: "
"enabled not set";
return base::nullopt;
}
return base::Optional<InteractionCallback>(base::BindRepeating(
&android_interactions::SetViewEnabled, user_model_->GetWeakPtr(),
proto.set_view_enabled(), view_handler_));
case CallbackProto::kShowGenericPopup:
if (proto.show_generic_popup().popup_identifier().empty()) {
VLOG(1) << "Error creating ShowGenericPopup interaction: "
"popup_identifier not set";
return base::nullopt;
}
return base::Optional<InteractionCallback>(base::BindRepeating(
&InteractionHandlerAndroid::CreateAndShowGenericPopup, GetWeakPtr(),
proto.show_generic_popup()));
case CallbackProto::kCreateNestedUi:
if (proto.create_nested_ui().generic_ui_identifier().empty()) {
VLOG(1) << "Error creating CreateNestedGenericUi interaction: "
"generic_ui_identifier not set";
return base::nullopt;
}
return base::Optional<InteractionCallback>(base::BindRepeating(
&InteractionHandlerAndroid::CreateAndAttachNestedGenericUi,
GetWeakPtr(), proto.create_nested_ui()));
case CallbackProto::kClearViewContainer:
if (proto.clear_view_container().view_identifier().empty()) {
VLOG(1) << "Error creating ClearViewContainer interaction: "
"view_identifier not set";
return base::nullopt;
}
return base::Optional<InteractionCallback>(
base::BindRepeating(&android_interactions::ClearViewContainer,
proto.clear_view_container().view_identifier(),
view_handler_, jdelegate_));
case CallbackProto::kForEach: {
if (proto.for_each().loop_counter().empty()) {
VLOG(1) << "Error creating ForEach interaction: "
"loop_counter not set";
return base::nullopt;
}
if (proto.for_each().loop_value_model_identifier().empty()) {
VLOG(1) << "Error creating ForEach interaction: "
"loop_value_model_identifier not set";
return base::nullopt;
}
// Parse the callbacks here to fail view inflation in case of invalid
// callbacks.
for (const auto& callback_proto : proto.for_each().callbacks()) {
auto callback = CreateInteractionCallbackFromProto(callback_proto);
if (!callback.has_value()) {
VLOG(1) << "Error creating ForEach interaction: failed to create "
"callback";
return base::nullopt;
}
}
return base::Optional<InteractionCallback>(base::BindRepeating(
&RunForEachLoop, proto.for_each(), GetWeakPtr(),
user_model_->GetWeakPtr(), view_handler_->GetWeakPtr()));
}
case CallbackProto::KIND_NOT_SET:
VLOG(1) << "Error creating interaction: kind not set";
return base::nullopt;
}
}
void InteractionHandlerAndroid::DeleteNestedUi(const std::string& identifier) {
auto it = nested_ui_controllers_.find(identifier);
if (it != nested_ui_controllers_.end()) {
nested_ui_controllers_.erase(it);
}
}
const GenericUiNestedControllerAndroid*
InteractionHandlerAndroid::CreateNestedUi(
const GenericUserInterfaceProto& proto,
const std::string& identifier) {
if (nested_ui_controllers_.find(identifier) != nested_ui_controllers_.end()) {
VLOG(2) << "Error creating nested UI: " << identifier
<< " already exixsts (did you forget to clear the previous "
"instance with ClearViewContainerProto?)";
return nullptr;
}
auto nested_ui = GenericUiNestedControllerAndroid::CreateFromProto(
proto, jcontext_, jdelegate_, event_handler_, user_model_,
basic_interactions_, radio_button_controller_);
const auto* nested_ui_ptr = nested_ui.get();
if (nested_ui) {
nested_ui_controllers_.emplace(identifier, std::move(nested_ui));
} else {
VLOG(2) << "Error creating nested UI " << identifier
<< ": view inflation failed";
}
return nested_ui_ptr;
}
void InteractionHandlerAndroid::CreateAndAttachNestedGenericUi(
const CreateNestedGenericUiProto& proto) {
auto* nested_ui =
CreateNestedUi(proto.generic_ui(), proto.generic_ui_identifier());
if (!nested_ui) {
return;
}
if (!android_interactions::AttachViewToParent(nested_ui->GetRootView(),
proto.parent_view_identifier(),
view_handler_)) {
DeleteNestedUi(proto.generic_ui_identifier());
return;
}
AddInteraction(
{EventProto::kOnViewContainerCleared, proto.parent_view_identifier()},
base::BindRepeating(&InteractionHandlerAndroid::DeleteNestedUi,
GetWeakPtr(), proto.generic_ui_identifier()));
}
void InteractionHandlerAndroid::CreateAndShowGenericPopup(
const ShowGenericUiPopupProto& proto) {
auto* nested_ui =
CreateNestedUi(proto.generic_ui(), proto.popup_identifier());
if (!nested_ui) {
return;
}
AddInteraction({EventProto::kOnPopupDismissed, proto.popup_identifier()},
base::BindRepeating(&InteractionHandlerAndroid::DeleteNestedUi,
GetWeakPtr(), proto.popup_identifier()));
android_interactions::ShowGenericPopup(proto, nested_ui->GetRootView(),
jcontext_, jdelegate_);
}
void InteractionHandlerAndroid::RunValueChangedCallbacks() {
for (const auto& interaction : interactions_) {
if (interaction.first.first == EventProto::kOnValueChanged) {
OnEvent(interaction.first);
}
}
}
} // namespace autofill_assistant