| // 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/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/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/AssistantHeaderModel_jni.h" |
| #include "jni/AssistantModel_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), weak_ptr_factory_(this) { |
| DCHECK(web_contents); |
| DCHECK(client); |
| DCHECK(ui_delegate); |
| java_autofill_assistant_ui_controller_ = |
| Java_AutofillAssistantUiController_createAndStartUi( |
| AttachCurrentThread(), web_contents->GetJavaWebContents(), |
| reinterpret_cast<intptr_t>(this)); |
| } |
| |
| 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::ShowStatusMessage(const std::string& message) { |
| if (!message.empty()) { |
| JNIEnv* env = AttachCurrentThread(); |
| Java_AssistantHeaderModel_setStatusMessage( |
| env, GetHeaderModel(), |
| base::android::ConvertUTF8ToJavaString(env, message)); |
| } |
| } |
| |
| std::string UiControllerAndroid::GetStatusMessage() { |
| JNIEnv* env = AttachCurrentThread(); |
| base::android::ScopedJavaLocalRef<jstring> message = |
| Java_AssistantHeaderModel_getStatusMessage(env, GetHeaderModel()); |
| std::string status; |
| base::android::ConvertJavaStringToUTF8(env, message.obj(), &status); |
| return status; |
| } |
| |
| void UiControllerAndroid::ShowProgressBar(int progress, |
| const std::string& message) { |
| ShowStatusMessage(message); |
| // TODO(crbug.com/806868): Get progress first and call setProgress only if |
| // progress > current_progress, and remove that logic from |
| // AnimatedProgressBar. |
| Java_AssistantHeaderModel_setProgress(AttachCurrentThread(), GetHeaderModel(), |
| progress); |
| } |
| |
| void UiControllerAndroid::HideProgressBar() { |
| // TODO(crbug.com/806868): Remove calls to this function. |
| } |
| |
| void UiControllerAndroid::SetProgressPulsingEnabled(bool enabled) { |
| Java_AssistantHeaderModel_setProgressPulsingEnabled( |
| AttachCurrentThread(), GetHeaderModel(), enabled); |
| } |
| |
| // 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::ShowOverlay() { |
| Java_AutofillAssistantUiController_onShowOverlay( |
| AttachCurrentThread(), java_autofill_assistant_ui_controller_); |
| SetProgressPulsingEnabled(false); |
| } |
| |
| void UiControllerAndroid::HideOverlay() { |
| Java_AutofillAssistantUiController_onHideOverlay( |
| AttachCurrentThread(), java_autofill_assistant_ui_controller_); |
| |
| // Hiding the overlay generally means that the user is expected to interact |
| // with the page, so we enable progress bar pulsing animation. |
| SetProgressPulsingEnabled(true); |
| } |
| |
| void UiControllerAndroid::AllowShowingSoftKeyboard(bool enabled) { |
| Java_AutofillAssistantUiController_onAllowShowingSoftKeyboard( |
| AttachCurrentThread(), java_autofill_assistant_ui_controller_, enabled); |
| } |
| |
| void UiControllerAndroid::Shutdown() { |
| Java_AutofillAssistantUiController_onShutdown( |
| AttachCurrentThread(), java_autofill_assistant_ui_controller_); |
| } |
| |
| void UiControllerAndroid::ShutdownGracefully() { |
| Java_AutofillAssistantUiController_onShutdownGracefully( |
| AttachCurrentThread(), java_autofill_assistant_ui_controller_); |
| } |
| |
| void UiControllerAndroid::Close() { |
| Java_AutofillAssistantUiController_onClose( |
| AttachCurrentThread(), java_autofill_assistant_ui_controller_); |
| } |
| |
| void UiControllerAndroid::UpdateTouchableArea( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jobject>& obj) { |
| ui_delegate_->UpdateTouchableArea(); |
| } |
| |
| void UiControllerAndroid::OnUserInteractionInsideTouchableArea( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jobject>& jcallerj) { |
| ui_delegate_->OnUserInteractionInsideTouchableArea(); |
| } |
| |
| void UiControllerAndroid::OnChipSelected(JNIEnv* env, |
| const JavaParamRef<jobject>& jcaller, |
| jint index) { |
| if (current_chips_ && index >= 0 && index < (int)current_chips_->size()) { |
| auto callback = std::move((*current_chips_)[index].callback); |
| current_chips_.reset(); |
| std::move(callback).Run(); |
| } |
| } |
| |
| void UiControllerAndroid::OnGetPaymentInformation( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& jcaller, |
| jboolean jsucceed, |
| const JavaParamRef<jobject>& jcard, |
| const JavaParamRef<jobject>& jaddress, |
| const JavaParamRef<jstring>& jpayer_name, |
| const JavaParamRef<jstring>& jpayer_phone, |
| const JavaParamRef<jstring>& jpayer_email, |
| jboolean jis_terms_and_services_accepted) { |
| DCHECK(get_payment_information_callback_); |
| SetProgressPulsingEnabled(false); |
| |
| std::unique_ptr<PaymentInformation> payment_info = |
| std::make_unique<PaymentInformation>(); |
| payment_info->succeed = jsucceed; |
| payment_info->is_terms_and_conditions_accepted = |
| jis_terms_and_services_accepted; |
| if (payment_info->succeed) { |
| if (jcard != nullptr) { |
| payment_info->card = std::make_unique<autofill::CreditCard>(); |
| autofill::PersonalDataManagerAndroid::PopulateNativeCreditCardFromJava( |
| jcard, env, payment_info->card.get()); |
| |
| auto guid = payment_info->card->billing_address_id(); |
| if (!guid.empty()) { |
| autofill::AutofillProfile* profile = |
| autofill::PersonalDataManagerFactory::GetForProfile( |
| ProfileManager::GetLastUsedProfile()) |
| ->GetProfileByGUID(guid); |
| if (profile != nullptr) |
| payment_info->billing_address = |
| std::make_unique<autofill::AutofillProfile>(*profile); |
| } |
| } |
| if (jaddress != nullptr) { |
| payment_info->shipping_address = |
| std::make_unique<autofill::AutofillProfile>(); |
| autofill::PersonalDataManagerAndroid::PopulateNativeProfileFromJava( |
| jaddress, env, payment_info->shipping_address.get()); |
| } |
| if (jpayer_name != nullptr) { |
| base::android::ConvertJavaStringToUTF8(env, jpayer_name, |
| &payment_info->payer_name); |
| } |
| if (jpayer_phone != nullptr) { |
| base::android::ConvertJavaStringToUTF8(env, jpayer_phone, |
| &payment_info->payer_phone); |
| } |
| if (jpayer_email != nullptr) { |
| base::android::ConvertJavaStringToUTF8(env, jpayer_email, |
| &payment_info->payer_email); |
| } |
| } |
| std::move(get_payment_information_callback_).Run(std::move(payment_info)); |
| } |
| |
| base::android::ScopedJavaLocalRef<jstring> |
| UiControllerAndroid::OnRequestDebugContext( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jobject>& jcaller) { |
| return base::android::ConvertUTF8ToJavaString(env, GetDebugContext()); |
| } |
| |
| void UiControllerAndroid::SetChips(std::unique_ptr<std::vector<Chip>> chips) { |
| DCHECK(chips); |
| current_chips_ = std::move(chips); |
| |
| int types[current_chips_->size()]; |
| std::vector<std::string> texts; |
| int i = 0; |
| for (const auto& chip : *current_chips_) { |
| types[i++] = chip.type; |
| texts.emplace_back(chip.text); |
| } |
| |
| JNIEnv* env = AttachCurrentThread(); |
| Java_AutofillAssistantUiController_onSetChips( |
| env, java_autofill_assistant_ui_controller_, |
| base::android::ToJavaIntArray(env, types, current_chips_->size()), |
| base::android::ToJavaArrayOfStrings(env, texts)); |
| } |
| |
| void UiControllerAndroid::ClearChips() { |
| current_chips_.reset(); |
| Java_AutofillAssistantUiController_onClearChips( |
| AttachCurrentThread(), java_autofill_assistant_ui_controller_); |
| } |
| |
| void UiControllerAndroid::GetPaymentInformation( |
| payments::mojom::PaymentOptionsPtr payment_options, |
| base::OnceCallback<void(std::unique_ptr<PaymentInformation>)> callback, |
| const std::string& title, |
| const std::vector<std::string>& supported_basic_card_networks) { |
| DCHECK(!get_payment_information_callback_); |
| get_payment_information_callback_ = std::move(callback); |
| JNIEnv* env = AttachCurrentThread(); |
| SetProgressPulsingEnabled(true); |
| Java_AutofillAssistantUiController_onRequestPaymentInformation( |
| env, java_autofill_assistant_ui_controller_, |
| 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, |
| static_cast<int>(payment_options->shipping_type), |
| base::android::ConvertUTF8ToJavaString(env, title), |
| base::android::ToJavaArrayOfStrings(env, supported_basic_card_networks)); |
| } |
| |
| void UiControllerAndroid::HideDetails() { |
| Java_AutofillAssistantUiController_onHideDetails( |
| AttachCurrentThread(), java_autofill_assistant_ui_controller_); |
| } |
| |
| void UiControllerAndroid::ShowInitialDetails(const std::string& title, |
| const std::string& description, |
| const std::string& mid, |
| const std::string& date) { |
| JNIEnv* env = AttachCurrentThread(); |
| Java_AutofillAssistantUiController_onShowInitialDetails( |
| env, java_autofill_assistant_ui_controller_, |
| base::android::ConvertUTF8ToJavaString(env, title), |
| base::android::ConvertUTF8ToJavaString(env, description), |
| base::android::ConvertUTF8ToJavaString(env, mid), |
| base::android::ConvertUTF8ToJavaString(env, date)); |
| } |
| |
| void UiControllerAndroid::ShowDetails(const ShowDetailsProto& show_details, |
| base::OnceCallback<void(bool)> callback) { |
| show_details_callback_ = std::move(callback); |
| const DetailsChanges& change_flags = show_details.change_flags(); |
| ShowDetails(show_details, change_flags.user_approval_required(), |
| change_flags.highlight_title(), change_flags.highlight_date()); |
| |
| if (!change_flags.user_approval_required()) { |
| std::move(show_details_callback_).Run(true); |
| return; |
| } |
| |
| // Ask for user approval. |
| std::string previous_status_message = GetStatusMessage(); |
| ShowStatusMessage( |
| l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_DETAILS_DIFFER)); |
| SetProgressPulsingEnabled(true); |
| |
| // Continue button. |
| auto chips = std::make_unique<std::vector<Chip>>(); |
| chips->emplace_back(); |
| chips->back().text = |
| l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_CONTINUE_BUTTON); |
| chips->back().type = Chip::Type::BUTTON_FILLED_BLUE; |
| chips->back().callback = base::BindOnce( |
| &UiControllerAndroid::OnUserApproval, weak_ptr_factory_.GetWeakPtr(), |
| show_details, previous_status_message, /* success= */ true); |
| |
| // Go back button. |
| chips->emplace_back(); |
| chips->back().text = |
| l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_DETAILS_DIFFER_GO_BACK); |
| chips->back().type = Chip::Type::BUTTON_TEXT; |
| chips->back().callback = base::BindOnce( |
| &UiControllerAndroid::OnUserApproval, weak_ptr_factory_.GetWeakPtr(), |
| show_details, previous_status_message, /* success= */ false); |
| |
| SetChips(std::move(chips)); |
| } |
| |
| void UiControllerAndroid::OnUserApproval( |
| const ShowDetailsProto& show_details, |
| const std::string& previous_status_message, |
| bool success) { |
| if (success) { |
| // Restore previous state. |
| ShowStatusMessage(previous_status_message); |
| SetProgressPulsingEnabled(false); |
| |
| // Show details without highlighted fields. |
| ShowDetails(show_details, /* user_approval_required= */ false, |
| /* highlight_title= */ false, /* highlight_date= */ false); |
| } |
| |
| if (show_details_callback_) { |
| std::move(show_details_callback_).Run(success); |
| } |
| } |
| |
| void UiControllerAndroid::ShowDetails(const ShowDetailsProto& show_details, |
| bool user_approval_required, |
| bool highlight_title, |
| bool highlight_date) { |
| const DetailsProto& details = show_details.details(); |
| int year = details.datetime().date().year(); |
| int month = details.datetime().date().month(); |
| int day = details.datetime().date().day(); |
| int hour = details.datetime().time().hour(); |
| int minute = details.datetime().time().minute(); |
| int second = details.datetime().time().second(); |
| |
| JNIEnv* env = AttachCurrentThread(); |
| return Java_AutofillAssistantUiController_onShowDetails( |
| env, java_autofill_assistant_ui_controller_, |
| base::android::ConvertUTF8ToJavaString(env, details.title()), |
| base::android::ConvertUTF8ToJavaString(env, details.url()), |
| base::android::ConvertUTF8ToJavaString(env, details.description()), |
| base::android::ConvertUTF8ToJavaString(env, details.m_id()), |
| base::android::ConvertUTF8ToJavaString(env, details.total_price()), year, |
| month, day, hour, minute, second, user_approval_required, highlight_title, |
| highlight_date); |
| } |
| |
| void UiControllerAndroid::Stop( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jobject>& obj) { |
| client_->Stop(); |
| } |
| |
| void UiControllerAndroid::UpdateTouchableArea(bool enabled, |
| 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_AutofillAssistantUiController_updateTouchableArea( |
| env, java_autofill_assistant_ui_controller_, enabled, |
| base::android::ToJavaFloatArray(env, flattened)); |
| SetProgressPulsingEnabled(true); |
| } |
| |
| void UiControllerAndroid::ExpandBottomSheet() { |
| Java_AutofillAssistantUiController_expandBottomSheet( |
| AttachCurrentThread(), java_autofill_assistant_ui_controller_); |
| } |
| |
| std::string UiControllerAndroid::GetDebugContext() const { |
| return ui_delegate_->GetDebugContext(); |
| } |
| } // namespace autofill_assistant. |