blob: 0e9498f62b87d6299622582146833bc4b1a41e04 [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/task/post_task.h"
#include "chrome/browser/android/chrome_feature_list.h"
#include "chrome/browser/autofill/android/personal_data_manager_android.h"
#include "chrome/browser/autofill/personal_data_manager_factory.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/autofill_profile.h"
#include "components/autofill/core/browser/credit_card.h"
#include "components/autofill_assistant/browser/controller.h"
#include "components/autofill_assistant/browser/metrics.h"
#include "components/autofill_assistant/browser/rectf.h"
#include "components/signin/core/browser/account_info.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 "jni/AssistantCarouselModel_jni.h"
#include "jni/AssistantDetailsModel_jni.h"
#include "jni/AssistantDetails_jni.h"
#include "jni/AssistantHeaderModel_jni.h"
#include "jni/AssistantModel_jni.h"
#include "jni/AssistantOverlayModel_jni.h"
#include "jni/AssistantPaymentRequestModel_jni.h"
#include "jni/AutofillAssistantUiController_jni.h"
#include "services/identity/public/cpp/identity_manager.h"
#include "ui/base/l10n/l10n_util.h"
using base::android::AttachCurrentThread;
using base::android::JavaParamRef;
using base::android::JavaRef;
namespace autofill_assistant {
UiControllerAndroid::UiControllerAndroid(content::WebContents* web_contents,
Client* client,
UiDelegate* ui_delegate)
: client_(client),
ui_delegate_(ui_delegate),
overlay_delegate_(this),
header_delegate_(this),
payment_request_delegate_(this),
carousel_delegate_(this),
weak_ptr_factory_(this) {
DCHECK(web_contents);
DCHECK(client);
DCHECK(ui_delegate);
JNIEnv* env = AttachCurrentThread();
java_autofill_assistant_ui_controller_ =
Java_AutofillAssistantUiController_createAndStartUi(
env, web_contents->GetJavaWebContents(),
reinterpret_cast<intptr_t>(this));
// 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.
Java_AssistantHeaderModel_setDelegate(env, GetHeaderModel(),
header_delegate_.GetJavaObject());
// Register payment_request_delegate_ as delegate for the payment request UI.
Java_AssistantPaymentRequestModel_setDelegate(
env, GetPaymentRequestModel(), payment_request_delegate_.GetJavaObject());
if (ui_delegate->GetState() != AutofillAssistantState::INACTIVE) {
// The UI was created for an existing Controller.
OnStatusMessageChanged(ui_delegate->GetStatusMessage());
OnProgressChanged(ui_delegate->GetProgress());
OnDetailsChanged(ui_delegate->GetDetails());
OnChipsChanged(ui_delegate->GetChips());
OnPaymentRequestChanged(ui_delegate->GetPaymentRequestOptions());
std::vector<RectF> area;
ui_delegate->GetTouchableArea(&area);
OnTouchableAreaChanged(area);
OnStateChanged(ui_delegate->GetState());
}
}
UiControllerAndroid::~UiControllerAndroid() {
Java_AutofillAssistantUiController_clearNativePtr(
AttachCurrentThread(), java_autofill_assistant_ui_controller_);
}
base::android::ScopedJavaLocalRef<jobject> UiControllerAndroid::GetModel() {
return Java_AutofillAssistantUiController_getModel(
AttachCurrentThread(), java_autofill_assistant_ui_controller_);
}
// Header related methods.
base::android::ScopedJavaLocalRef<jobject>
UiControllerAndroid::GetHeaderModel() {
return Java_AssistantModel_getHeaderModel(AttachCurrentThread(), GetModel());
}
void UiControllerAndroid::OnStateChanged(AutofillAssistantState new_state) {
switch (new_state) {
case AutofillAssistantState::STARTING:
SetOverlayState(OverlayState::FULL);
AllowShowingSoftKeyboard(false);
SetProgressPulsingEnabled(false);
SetAllowSwipingSheet(true);
return;
case AutofillAssistantState::RUNNING:
SetOverlayState(OverlayState::FULL);
AllowShowingSoftKeyboard(false);
SetProgressPulsingEnabled(false);
SetAllowSwipingSheet(true);
return;
case AutofillAssistantState::AUTOSTART_FALLBACK_PROMPT:
SetOverlayState(OverlayState::HIDDEN);
AllowShowingSoftKeyboard(true);
SetProgressPulsingEnabled(true);
// user interaction is needed.
ExpandBottomSheet();
return;
case AutofillAssistantState::PROMPT:
SetOverlayState(OverlayState::PARTIAL);
AllowShowingSoftKeyboard(true);
SetProgressPulsingEnabled(true);
SetAllowSwipingSheet(ui_delegate_->GetPaymentRequestOptions() == nullptr);
// user interaction is needed.
ExpandBottomSheet();
return;
case AutofillAssistantState::MODAL_DIALOG:
SetOverlayState(OverlayState::FULL);
AllowShowingSoftKeyboard(true);
SetProgressPulsingEnabled(false);
SetAllowSwipingSheet(true);
return;
case AutofillAssistantState::STOPPED:
SetOverlayState(OverlayState::HIDDEN);
AllowShowingSoftKeyboard(true);
SetProgressPulsingEnabled(false);
SetAllowSwipingSheet(true);
// make sure user sees the error message.
ExpandBottomSheet();
return;
case AutofillAssistantState::INACTIVE:
// TODO(crbug.com/806868): Add support for switching back to the inactive
// state, which is the initial state. We never enter it, so there should
// never be a call OnStateChanged(INACTIVE)
NOTREACHED() << "Switching to the inactive state is not supported.";
return;
}
NOTREACHED() << "Unknown state: " << static_cast<int>(new_state);
}
void UiControllerAndroid::OnStatusMessageChanged(const std::string& message) {
if (!message.empty()) {
JNIEnv* env = AttachCurrentThread();
Java_AssistantHeaderModel_setStatusMessage(
env, GetHeaderModel(),
base::android::ConvertUTF8ToJavaString(env, message));
}
}
void UiControllerAndroid::OnProgressChanged(int progress) {
Java_AssistantHeaderModel_setProgress(AttachCurrentThread(), GetHeaderModel(),
progress);
}
void UiControllerAndroid::AllowShowingSoftKeyboard(bool enabled) {
Java_AssistantModel_setAllowSoftKeyboard(AttachCurrentThread(), GetModel(),
enabled);
}
void UiControllerAndroid::ExpandBottomSheet() {
Java_AutofillAssistantUiController_expandBottomSheet(
AttachCurrentThread(), java_autofill_assistant_ui_controller_);
}
void UiControllerAndroid::SetProgressPulsingEnabled(bool enabled) {
Java_AssistantHeaderModel_setProgressPulsingEnabled(
AttachCurrentThread(), GetHeaderModel(), enabled);
}
void UiControllerAndroid::SetAllowSwipingSheet(bool allow) {
Java_AssistantModel_setAllowSwipingSheet(AttachCurrentThread(), GetModel(),
allow);
}
void UiControllerAndroid::OnFeedbackButtonClicked() {
JNIEnv* env = AttachCurrentThread();
Java_AutofillAssistantUiController_showFeedback(
env, java_autofill_assistant_ui_controller_,
base::android::ConvertUTF8ToJavaString(env, GetDebugContext()));
}
void UiControllerAndroid::OnCloseButtonClicked() {
JNIEnv* env = AttachCurrentThread();
// TODO(crbug.com/806868): Move here the logic that shutdowns immediately if
// close button is clicked during graceful shutdown.
Java_AutofillAssistantUiController_dismissAndShowSnackbar(
env, java_autofill_assistant_ui_controller_,
base::android::ConvertUTF8ToJavaString(
env, l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_STOPPED)),
Metrics::SHEET_CLOSED);
}
std::string UiControllerAndroid::GetDebugContext() {
if (captured_debug_context_.empty() && ui_delegate_) {
return ui_delegate_->GetDebugContext();
}
return captured_debug_context_;
}
// Carousel related methods.
base::android::ScopedJavaLocalRef<jobject>
UiControllerAndroid::GetCarouselModel() {
return Java_AssistantModel_getCarouselModel(AttachCurrentThread(),
GetModel());
}
void UiControllerAndroid::OnChipsChanged(const std::vector<Chip>& chips) {
JNIEnv* env = AttachCurrentThread();
auto jmodel = GetCarouselModel();
if (chips.empty()) {
Java_AssistantCarouselModel_clearChips(env, jmodel);
return;
}
std::vector<int> types;
std::vector<std::string> texts;
for (const auto& chip : chips) {
types.emplace_back(chip.type);
texts.emplace_back(chip.text);
}
Java_AssistantCarouselModel_setChips(
env, jmodel, base::android::ToJavaIntArray(env, types),
base::android::ToJavaArrayOfStrings(env, texts),
carousel_delegate_.GetJavaObject());
}
void UiControllerAndroid::OnChipSelected(int index) {
if (ui_delegate_)
ui_delegate_->SelectChip(index);
}
// Overlay related methods.
base::android::ScopedJavaLocalRef<jobject>
UiControllerAndroid::GetOverlayModel() {
return Java_AssistantModel_getOverlayModel(AttachCurrentThread(), GetModel());
}
void UiControllerAndroid::SetOverlayState(OverlayState state) {
Java_AssistantOverlayModel_setState(AttachCurrentThread(), GetOverlayModel(),
state);
}
void UiControllerAndroid::OnTouchableAreaChanged(
const std::vector<RectF>& areas) {
JNIEnv* env = AttachCurrentThread();
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);
}
Java_AssistantOverlayModel_setTouchableArea(
env, GetOverlayModel(), base::android::ToJavaFloatArray(env, flattened));
}
void UiControllerAndroid::OnUnexpectedTaps() {
JNIEnv* env = AttachCurrentThread();
Java_AutofillAssistantUiController_dismissAndShowSnackbar(
env, java_autofill_assistant_ui_controller_,
base::android::ConvertUTF8ToJavaString(
env, l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_MAYBE_GIVE_UP)),
Metrics::OVERLAY_STOP);
}
void UiControllerAndroid::UpdateTouchableArea() {
if (ui_delegate_)
ui_delegate_->UpdateTouchableArea();
}
void UiControllerAndroid::OnUserInteractionInsideTouchableArea() {
if (ui_delegate_)
ui_delegate_->OnUserInteractionInsideTouchableArea();
}
// Other methods.
void UiControllerAndroid::ShowOnboarding(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& on_accept) {
Java_AutofillAssistantUiController_onShowOnboarding(
env, java_autofill_assistant_ui_controller_, on_accept);
}
void UiControllerAndroid::Destroy() {
Java_AutofillAssistantUiController_destroy(
AttachCurrentThread(), java_autofill_assistant_ui_controller_,
/* delayed= */ false);
}
void UiControllerAndroid::WillShutdown(Metrics::DropOutReason reason) {
JNIEnv* env = AttachCurrentThread();
Java_AutofillAssistantUiController_destroy(
env, java_autofill_assistant_ui_controller_,
/* delayed= */ ui_delegate_->GetState() ==
AutofillAssistantState::STOPPED);
if (reason == Metrics::CUSTOM_TAB_CLOSED) {
Java_AutofillAssistantUiController_scheduleCloseCustomTab(
env, java_autofill_assistant_ui_controller_);
}
// Capture the debug context, for including into a feedback possibly sent
// later, after the delegate has been destroyed.
captured_debug_context_ = ui_delegate_->GetDebugContext();
ui_delegate_ = nullptr;
}
// Payment request related methods.
base::android::ScopedJavaLocalRef<jobject>
UiControllerAndroid::GetPaymentRequestModel() {
return Java_AssistantModel_getPaymentRequestModel(AttachCurrentThread(),
GetModel());
}
void UiControllerAndroid::OnGetPaymentInformation(
std::unique_ptr<PaymentInformation> payment_info) {
ui_delegate_->SetPaymentInformation(std::move(payment_info));
}
void UiControllerAndroid::OnPaymentRequestChanged(
const PaymentRequestOptions* payment_options) {
JNIEnv* env = AttachCurrentThread();
auto jmodel = GetPaymentRequestModel();
if (!payment_options) {
Java_AssistantPaymentRequestModel_clearOptions(env, jmodel);
return;
}
Java_AssistantPaymentRequestModel_setOptions(
env, jmodel,
base::android::ConvertUTF8ToJavaString(env,
client_->GetAccountEmailAddress()),
payment_options->request_shipping, payment_options->request_payer_name,
payment_options->request_payer_phone,
payment_options->request_payer_email,
base::android::ToJavaArrayOfStrings(
env, payment_options->supported_basic_card_networks));
}
// 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;
}
const DetailsProto& proto = details->proto;
auto jdetails = Java_AssistantDetails_create(
env, base::android::ConvertUTF8ToJavaString(env, proto.title()),
base::android::ConvertUTF8ToJavaString(env, proto.url()),
base::android::ConvertUTF8ToJavaString(env, proto.description()),
base::android::ConvertUTF8ToJavaString(env, proto.m_id()),
base::android::ConvertUTF8ToJavaString(env, proto.total_price()),
base::android::ConvertUTF8ToJavaString(env, details->datetime),
proto.datetime().date().year(), proto.datetime().date().month(),
proto.datetime().date().day(), proto.datetime().time().hour(),
proto.datetime().time().minute(), proto.datetime().time().second(),
details->changes.user_approval_required(),
details->changes.highlight_title(), details->changes.highlight_date());
Java_AssistantDetailsModel_setDetails(env, jmodel, jdetails);
}
void UiControllerAndroid::Stop(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
int jreason) {
client_->Shutdown(static_cast<Metrics::DropOutReason>(jreason));
}
void UiControllerAndroid::DestroyUI(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
client_->DestroyUI();
}
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));
}
} // namespace autofill_assistant.