chromium/chrome/browser/ui/android/autofill/manual_filling_view_android.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/android/autofill/manual_filling_view_android.h"

#include <jni.h>

#include <map>
#include <memory>
#include <string>
#include <vector>

#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "chrome/browser/keyboard_accessory/android/accessory_sheet_data.h"
#include "chrome/browser/keyboard_accessory/android/accessory_sheet_enums.h"
#include "chrome/browser/keyboard_accessory/android/manual_filling_controller.h"
#include "chrome/browser/keyboard_accessory/android/manual_filling_controller_impl.h"
#include "chrome/browser/password_manager/chrome_password_manager_client.h"
#include "components/password_manager/core/browser/credential_cache.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/password_manager/core/browser/password_form_manager.h"
#include "content/public/browser/web_contents.h"
#include "ui/android/view_android.h"
#include "ui/android/window_android.h"
#include "url/android/gurl_android.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/android/features/keyboard_accessory/internal/jni/ManualFillingComponentBridge_jni.h"
#include "chrome/android/features/keyboard_accessory/public/jni/UserInfoField_jni.h"

using autofill::AccessorySheetData;
using autofill::AccessorySheetField;
using autofill::FooterCommand;
using autofill::UserInfo;
using autofill::password_generation::PasswordGenerationUIData;
using base::android::ConvertJavaStringToUTF16;
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertUTF16ToJavaString;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaRef;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
using password_manager::PasswordForm;

namespace {

AccessorySheetField ConvertJavaUserInfoField(
    JNIEnv* env,
    const JavaRef<jobject>& j_field_to_convert) {
  std::u16string display_text = ConvertJavaStringToUTF16(
      env, Java_UserInfoField_getDisplayText(env, j_field_to_convert));
  std::u16string text_to_fill = ConvertJavaStringToUTF16(
      env, Java_UserInfoField_getTextToFill(env, j_field_to_convert));
  std::u16string a11y_description = ConvertJavaStringToUTF16(
      env, Java_UserInfoField_getA11yDescription(env, j_field_to_convert));
  std::string id = ConvertJavaStringToUTF8(
      env, Java_UserInfoField_getId(env, j_field_to_convert));
  bool is_obfuscated = Java_UserInfoField_isObfuscated(env, j_field_to_convert);
  bool selectable = Java_UserInfoField_isSelectable(env, j_field_to_convert);
  return AccessorySheetField::Builder()
      .SetDisplayText(std::move(display_text))
      .SetTextToFill(std::move(text_to_fill))
      .SetA11yDescription(std::move(a11y_description))
      .SetId(std::move(id))
      .SetIsObfuscated(is_obfuscated)
      .SetSelectable(selectable)
      .Build();
}

// The Conversion does not require any actual methods from either side of the
// bridge — it's only required because it is referenced in callbacks. Therefore,
// the java_object can always be used, even if the controller has been
// dismissed.
// TODO(crbug.com/40858913): Pass a delegate/callback and not the bridge object.
ScopedJavaGlobalRef<jobject> ConvertAccessorySheetDataToJavaObject(
    ScopedJavaGlobalRef<jobject> java_object,
    AccessorySheetData tab_data) {
  // Keep the ManualFillingViewAndroid:: prefix for easier trace comparison.
  TRACE_EVENT0(
      "passwords",
      "ManualFillingViewAndroid::ConvertAccessorySheetDataToJavaObject");
  DCHECK(java_object);
  JNIEnv* env = base::android::AttachCurrentThread();
  ScopedJavaGlobalRef<jobject> j_tab_data;
  j_tab_data.Reset(Java_ManualFillingComponentBridge_createAccessorySheetData(
      env, static_cast<int>(tab_data.get_sheet_type()),
      tab_data.user_info_title(), tab_data.plus_address_title(),
      tab_data.warning()));

  if (tab_data.option_toggle().has_value()) {
    const autofill::OptionToggle& toggle = tab_data.option_toggle().value();
    Java_ManualFillingComponentBridge_addOptionToggleToAccessorySheetData(
        env, java_object, j_tab_data, toggle.display_text(),
        toggle.is_enabled(), static_cast<int>(toggle.accessory_action()));
  }

  for (const autofill::PlusAddressInfo& plus_address_info :
       tab_data.plus_address_info_list()) {
    Java_ManualFillingComponentBridge_addPlusAddressInfoToAccessorySheetData(
        env, java_object, j_tab_data,
        static_cast<int>(tab_data.get_sheet_type()), plus_address_info.origin(),
        plus_address_info.plus_address().display_text());
  }

  for (const autofill::PasskeySection& passkey_section :
       tab_data.passkey_section_list()) {
    Java_ManualFillingComponentBridge_addPasskeySectionToAccessorySheetData(
        env, java_object, j_tab_data,
        static_cast<int>(tab_data.get_sheet_type()),
        passkey_section.display_name(), passkey_section.passkey_id());
  }

  for (const UserInfo& user_info : tab_data.user_info_list()) {
    ScopedJavaLocalRef<jobject> j_user_info =
        Java_ManualFillingComponentBridge_addUserInfoToAccessorySheetData(
            env, java_object, j_tab_data, user_info.origin(),
            user_info.is_exact_match().value(),
            url::GURLAndroid::FromNativeGURL(env, user_info.icon_url()));
    for (const AccessorySheetField& field : user_info.fields()) {
      Java_ManualFillingComponentBridge_addFieldToUserInfo(
          env, java_object, j_user_info,
          static_cast<int>(tab_data.get_sheet_type()), field.display_text(),
          field.text_to_fill(), field.a11y_description(), field.id(),
          field.icon_id(), field.is_obfuscated(), field.selectable());
    }
  }

  for (const autofill::PromoCodeInfo& promo_code_info :
       tab_data.promo_code_info_list()) {
    const AccessorySheetField& promo_code = promo_code_info.promo_code();
    const std::u16string& detailsText = promo_code_info.details_text();
    Java_ManualFillingComponentBridge_addPromoCodeInfoToAccessorySheetData(
        env, java_object, j_tab_data,
        static_cast<int>(tab_data.get_sheet_type()), promo_code.display_text(),
        promo_code.text_to_fill(), promo_code.a11y_description(),
        promo_code.id(), promo_code.is_obfuscated(), detailsText);
  }

  for (const autofill::IbanInfo& iban_info : tab_data.iban_info_list()) {
    const AccessorySheetField& value = iban_info.value();
    Java_ManualFillingComponentBridge_addIbanInfoToAccessorySheetData(
        env, java_object, j_tab_data,
        static_cast<int>(tab_data.get_sheet_type()), value.id(),
        value.display_text(), value.text_to_fill());
  }

  for (const FooterCommand& footer_command : tab_data.footer_commands()) {
    Java_ManualFillingComponentBridge_addFooterCommandToAccessorySheetData(
        env, java_object, j_tab_data, footer_command.display_text(),
        static_cast<int>(footer_command.accessory_action()));
  }
  return j_tab_data;
}

}  // namespace

ManualFillingViewAndroid::ManualFillingViewAndroid(
    ManualFillingController* controller,
    content::WebContents* web_contents)
    : controller_(controller), web_contents_(web_contents) {}

ManualFillingViewAndroid::~ManualFillingViewAndroid() {
  if (!java_object_internal_) {
    return;  // No work to do.
  }
  Java_ManualFillingComponentBridge_destroy(
      base::android::AttachCurrentThread(), java_object_internal_);
  java_object_internal_.Reset(nullptr);
}

void ManualFillingViewAndroid::OnItemsAvailable(AccessorySheetData data) {
  TRACE_EVENT0("passwords", "ManualFillingViewAndroid::OnItemsAvailable");
  if (auto obj = GetOrCreateJavaObject()) {
    background_task_runner_->PostTaskAndReplyWithResult(
        FROM_HERE,
        base::BindOnce(&ConvertAccessorySheetDataToJavaObject, obj,
                       std::move(data)),
        base::BindOnce(&Java_ManualFillingComponentBridge_onItemsAvailable,
                       base::android::AttachCurrentThread(), obj));
  }
}

void ManualFillingViewAndroid::CloseAccessorySheet() {
  if (auto obj = GetOrCreateJavaObject()) {
    Java_ManualFillingComponentBridge_closeAccessorySheet(
        base::android::AttachCurrentThread(), obj);
  }
}

void ManualFillingViewAndroid::SwapSheetWithKeyboard() {
  if (auto obj = GetOrCreateJavaObject()) {
    Java_ManualFillingComponentBridge_swapSheetWithKeyboard(
        base::android::AttachCurrentThread(), obj);
  }
}

void ManualFillingViewAndroid::Show(WaitForKeyboard wait_for_keyboard) {
  TRACE_EVENT0("passwords", "ManualFillingViewAndroid::Show");
  if (auto obj = GetOrCreateJavaObject()) {
    Java_ManualFillingComponentBridge_show(base::android::AttachCurrentThread(),
                                           obj, wait_for_keyboard.value());
  }
}

void ManualFillingViewAndroid::Hide() {
  if (auto obj = GetOrCreateJavaObject()) {
    Java_ManualFillingComponentBridge_hide(base::android::AttachCurrentThread(),
                                           obj);
  }
}

void ManualFillingViewAndroid::ShowAccessorySheetTab(
    const autofill::AccessoryTabType& tab_type) {
  if (auto obj = GetOrCreateJavaObject()) {
    Java_ManualFillingComponentBridge_showAccessorySheetTab(
        base::android::AttachCurrentThread(), obj, static_cast<int>(tab_type));
  }
}
void ManualFillingViewAndroid::OnAccessoryActionAvailabilityChanged(
    ShouldShowAction shouldShowAction,
    autofill::AccessoryAction action) {
  if (!shouldShowAction && java_object_internal_.is_null()) {
    return;
  }
  if (auto obj = GetOrCreateJavaObject()) {
    Java_ManualFillingComponentBridge_onAccessoryActionAvailabilityChanged(
        base::android::AttachCurrentThread(), obj, shouldShowAction.value(),
        static_cast<int>(action));
  }
}

void ManualFillingViewAndroid::OnFillingTriggered(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& obj,
    jint tab_type,
    const base::android::JavaParamRef<jobject>& j_user_info_field) {
  controller_->OnFillingTriggered(
      static_cast<autofill::AccessoryTabType>(tab_type),
      ConvertJavaUserInfoField(env, j_user_info_field));
}

void ManualFillingViewAndroid::OnPasskeySelected(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& obj,
    jint tab_type,
    std::vector<uint8_t>& passkey) {
  controller_->OnPasskeySelected(
      static_cast<autofill::AccessoryTabType>(tab_type), passkey);
}

void ManualFillingViewAndroid::OnOptionSelected(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& obj,
    jint selected_action) {
  controller_->OnOptionSelected(
      static_cast<autofill::AccessoryAction>(selected_action));
}

void ManualFillingViewAndroid::OnToggleChanged(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& obj,
    jint selected_action,
    jboolean enabled) {
  controller_->OnToggleChanged(
      static_cast<autofill::AccessoryAction>(selected_action), enabled);
}

void ManualFillingViewAndroid::RequestAccessorySheet(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& obj,
    jint tab_type) {
  // controller_ owns this class. Therefore, the callback can't outlive the view
  // and base::Unretained is always a valid reference.
  controller_->RequestAccessorySheet(
      static_cast<autofill::AccessoryTabType>(tab_type),
      base::BindOnce(&ManualFillingViewAndroid::OnItemsAvailable,
                     base::Unretained(this)));
}

void ManualFillingViewAndroid::OnViewDestroyed(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& obj) {
  java_object_internal_.Reset(nullptr);
}

base::android::ScopedJavaGlobalRef<jobject>
ManualFillingViewAndroid::GetOrCreateJavaObject() {
  if (java_object_internal_) {
    return java_object_internal_;
  }
  if (controller_->container_view() == nullptr ||
      controller_->container_view()->GetWindowAndroid() == nullptr) {
    return nullptr;  // No window attached (yet or anymore).
  }
  java_object_internal_.Reset(Java_ManualFillingComponentBridge_create(
      base::android::AttachCurrentThread(), reinterpret_cast<intptr_t>(this),
      controller_->container_view()->GetWindowAndroid()->GetJavaObject(),
      web_contents_->GetJavaWebContents()));
  return java_object_internal_;
}

// static
void JNI_ManualFillingComponentBridge_CachePasswordSheetDataForTesting(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& j_web_contents,
    std::vector<std::string>& usernames,
    std::vector<std::string>& passwords,
    jboolean j_blocklisted) {
  content::WebContents* web_contents =
      content::WebContents::FromJavaWebContents(j_web_contents);

  url::Origin origin =
      web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin();
  std::vector<password_manager::PasswordForm> credentials(usernames.size());
  for (unsigned int i = 0; i < usernames.size(); ++i) {
    credentials[i].url = origin.GetURL();
    credentials[i].username_value = base::ASCIIToUTF16(usernames[i]);
    credentials[i].password_value = base::ASCIIToUTF16(passwords[i]);
    credentials[i].match_type =
        password_manager::PasswordForm::MatchType::kExact;
  }
  return ChromePasswordManagerClient::FromWebContents(web_contents)
      ->GetCredentialCacheForTesting()
      ->SaveCredentialsAndBlocklistedForOrigin(
          credentials,
          password_manager::CredentialCache::IsOriginBlocklisted(j_blocklisted),
          origin);
}

// static
void JNI_ManualFillingComponentBridge_NotifyFocusedFieldTypeForTesting(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& j_web_contents,
    jlong j_focused_field_id,
    jint j_available) {
  ManualFillingControllerImpl::GetOrCreate(
      content::WebContents::FromJavaWebContents(j_web_contents))
      ->NotifyFocusedInputChanged(
          autofill::FieldRendererId(j_focused_field_id),
          static_cast<autofill::mojom::FocusedFieldType>(j_available));
}

// static
void JNI_ManualFillingComponentBridge_SignalAutoGenerationStatusForTesting(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& j_web_contents,
    jboolean j_available) {
  content::WebContents* web_contents =
      content::WebContents::FromJavaWebContents(j_web_contents);

  // Bypass the generation controller when sending this status to the UI to
  // avoid setup overhead, since its logic is currently not needed for tests.
  ManualFillingControllerImpl::GetOrCreate(web_contents)
      ->OnAccessoryActionAvailabilityChanged(
          ManualFillingController::ShouldShowAction(j_available),
          autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC);
}

// static
void JNI_ManualFillingComponentBridge_DisableServerPredictionsForTesting(
    JNIEnv* env) {
  password_manager::PasswordFormManager::
      DisableFillingServerPredictionsForTesting();
}

// static
std::unique_ptr<ManualFillingViewInterface> ManualFillingViewInterface::Create(
    ManualFillingController* controller,
    content::WebContents* web_contents) {
  return std::make_unique<ManualFillingViewAndroid>(controller, web_contents);
}