blob: a6230d4fed21808c484f9f8a2da27bebf2d99646 [file] [log] [blame]
// Copyright 2020 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/trigger_script_bridge_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/base64url.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/browser/password_manager/chrome_password_manager_client.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"
#include "components/autofill_assistant/browser/service/service_request_sender_impl.h"
#include "components/autofill_assistant/browser/service/service_request_sender_local_impl.h"
#include "components/autofill_assistant/browser/service/simple_url_loader_factory.h"
#include "components/autofill_assistant/browser/string_conversions_util.h"
#include "components/autofill_assistant/browser/trigger_scripts/dynamic_trigger_conditions.h"
#include "components/autofill_assistant/browser/trigger_scripts/static_trigger_conditions.h"
#include "components/autofill_assistant/browser/web/web_controller.h"
#include "components/autofill_assistant/browser/website_login_manager_impl.h"
#include "content/public/browser/web_contents.h"
#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 {
bool IsFirstTimeTriggerScriptUser() {
return autofill_assistant::
Java_AssistantTriggerScriptBridge_isFirstTimeTriggerScriptUser(
AttachCurrentThread());
}
} // namespace
namespace autofill_assistant {
TriggerScriptBridgeAndroid::TriggerScriptBridgeAndroid() = default;
TriggerScriptBridgeAndroid::~TriggerScriptBridgeAndroid() = default;
void TriggerScriptBridgeAndroid::StartTriggerScript(
content::WebContents* web_contents,
const JavaParamRef<jobject>& jdelegate,
const GURL& initial_url,
std::unique_ptr<TriggerContext> trigger_context,
jlong jservice_request_sender) {
DCHECK(!java_object_);
java_object_ = ScopedJavaGlobalRef<jobject>(jdelegate);
Java_AssistantTriggerScriptBridge_setNativePtr(
AttachCurrentThread(), java_object_, reinterpret_cast<intptr_t>(this));
std::unique_ptr<ServiceRequestSender> service_request_sender = nullptr;
if (jservice_request_sender) {
service_request_sender.reset(static_cast<ServiceRequestSender*>(
reinterpret_cast<void*>(jservice_request_sender)));
// TODO(b/171776026): consider exposing this in proto.
disable_header_animations_for_testing_ = true;
} else if (trigger_context->GetBase64TriggerScriptsResponseProto()
.has_value()) {
std::string response;
if (!base::Base64UrlDecode(
trigger_context->GetBase64TriggerScriptsResponseProto().value(),
base::Base64UrlDecodePolicy::IGNORE_PADDING, &response)) {
LOG(ERROR) << "Failed to base64-decode trigger scripts response";
Metrics::RecordLiteScriptFinished(
ukm::UkmRecorder::Get(), web_contents,
Metrics::LiteScriptFinishedState::LITE_SCRIPT_BASE64_DECODING_ERROR);
return;
}
service_request_sender =
std::make_unique<ServiceRequestSenderLocalImpl>(response);
} else {
service_request_sender = std::make_unique<ServiceRequestSenderImpl>(
web_contents->GetBrowserContext(),
/* access_token_fetcher = */ nullptr,
std::make_unique<NativeURLLoaderFactory>(),
ApiKeyFetcher().GetAPIKey(chrome::GetChannel()),
/* auth_enabled = */ false,
/* disable_auth_if_no_access_token = */ true);
}
ServerUrlFetcher url_fetcher{ServerUrlFetcher::GetDefaultServerUrl()};
if (!website_login_manager_) {
website_login_manager_ = std::make_unique<WebsiteLoginManagerImpl>(
ChromePasswordManagerClient::FromWebContents(web_contents),
web_contents);
}
trigger_script_coordinator_ = std::make_unique<TriggerScriptCoordinator>(
web_contents, website_login_manager_.get(),
base::BindRepeating(&IsFirstTimeTriggerScriptUser),
WebController::CreateForWebContents(web_contents),
std::move(service_request_sender),
url_fetcher.GetTriggerScriptsEndpoint(),
std::make_unique<StaticTriggerConditions>(),
std::make_unique<DynamicTriggerConditions>(), ukm::UkmRecorder::Get());
trigger_script_coordinator_->AddObserver(this);
trigger_script_coordinator_->Start(initial_url, std::move(trigger_context));
}
void TriggerScriptBridgeAndroid::StopTriggerScript() {
if (java_object_) {
Java_AssistantTriggerScriptBridge_clearNativePtr(AttachCurrentThread(),
java_object_);
java_object_ = nullptr;
}
trigger_script_coordinator_ = nullptr;
}
void TriggerScriptBridgeAndroid::OnTriggerScriptAction(
JNIEnv* env,
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 JavaParamRef<jobject>& jcaller) {
if (!trigger_script_coordinator_) {
return;
}
trigger_script_coordinator_->OnBottomSheetClosedWithSwipe();
}
bool TriggerScriptBridgeAndroid::OnBackButtonPressed(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller) {
if (!trigger_script_coordinator_) {
return false;
}
return trigger_script_coordinator_->OnBackButtonPressed();
}
void TriggerScriptBridgeAndroid::OnTabInteractabilityChanged(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
jboolean jinteractable) {
if (!trigger_script_coordinator_) {
return;
}
trigger_script_coordinator_->OnTabInteractabilityChanged(jinteractable);
}
void TriggerScriptBridgeAndroid::OnKeyboardVisibilityChanged(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
jboolean jvisible) {
if (!trigger_script_coordinator_) {
return;
}
trigger_script_coordinator_->OnKeyboardVisibilityChanged(jvisible);
}
void TriggerScriptBridgeAndroid::OnTriggerScriptShown(
const TriggerScriptUIProto& proto) {
if (!java_object_) {
return;
}
JNIEnv* env = AttachCurrentThread();
auto jheader_model =
Java_AssistantTriggerScriptBridge_createHeaderAndGetModel(env,
java_object_);
AssistantHeaderModel header_model(jheader_model);
if (disable_header_animations_for_testing_) {
header_model.SetDisableAnimations(disable_header_animations_for_testing_);
}
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()));
}
last_shown_trigger_script_ = proto;
jboolean success = 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),
proto.resize_visual_viewport());
trigger_script_coordinator_->OnTriggerScriptShown(success);
}
void TriggerScriptBridgeAndroid::OnTriggerScriptHidden() {
if (!java_object_) {
return;
}
Java_AssistantTriggerScriptBridge_hideTriggerScript(AttachCurrentThread(),
java_object_);
}
void TriggerScriptBridgeAndroid::OnTriggerScriptFinished(
Metrics::LiteScriptFinishedState state) {
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();
}
void TriggerScriptBridgeAndroid::OnVisibilityChanged(bool visible) {
if (!visible || !trigger_script_coordinator_) {
return;
}
// Every time the tab becomes visible again we have to double-check if the
// proactive help settings is still enabled.
trigger_script_coordinator_->OnProactiveHelpSettingChanged(
Java_AssistantTriggerScriptBridge_isProactiveHelpEnabled(
AttachCurrentThread()));
}
base::Optional<TriggerScriptUIProto>
TriggerScriptBridgeAndroid::GetLastShownTriggerScript() const {
return last_shown_trigger_script_;
}
void TriggerScriptBridgeAndroid::ClearLastShownTriggerScript() {
last_shown_trigger_script_.reset();
}
} // namespace autofill_assistant