chromium/chrome/browser/touch_to_fill/password_manager/password_generation/android/touch_to_fill_password_generation_controller_unittest.cc

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

#include <memory>
#include <string>
#include <tuple>

#include "base/memory/weak_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "chrome/browser/keyboard_accessory/test_utils/android/mock_manual_filling_controller.h"
#include "chrome/browser/password_manager/android/password_generation_element_data.h"
#include "chrome/browser/touch_to_fill/password_manager/password_generation/android/mock_touch_to_fill_password_generation_bridge.h"
#include "chrome/browser/touch_to_fill/password_manager/password_generation/android/touch_to_fill_password_generation_controller.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/autofill/core/common/password_generation_util.h"
#include "components/password_manager/content/browser/content_password_manager_driver.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/stub_password_manager_client.h"
#include "components/prefs/testing_pref_service.h"
#include "content/public/test/text_input_test_utils.h"
#include "ui/base/ime/mojom/text_input_state.mojom.h"
#include "ui/base/ime/text_input_type.h"

using autofill::password_generation::PasswordGenerationType;
using password_manager::metrics_util::GenerationDialogChoice;
using testing::_;
using testing::Combine;
using testing::Eq;
using testing::Values;
using ShouldShowAction = ManualFillingController::ShouldShowAction;
using TouchToFillPasswordGenerationControllerMetricsParam =
    std::tuple<PasswordGenerationType, bool, std::string>;

class TouchToFillPasswordGenerationControllerTest
    : public ChromeRenderViewHostTestHarness {
 public:
  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();
    password_manager_driver_ =
        std::make_unique<password_manager::ContentPasswordManagerDriver>(
            main_rfh(), &client_);
  }

  base::WeakPtr<password_manager::ContentPasswordManagerDriver>
  password_mananger_driver() {
    return password_manager_driver_->AsWeakPtrImpl();
  }

  TestingPrefServiceSimple* pref_service() { return &test_pref_service_; }

  base::MockCallback<base::OnceCallback<void()>> on_dismissed_callback_;
  const std::string test_user_account_ = "[email protected]";
  MockManualFillingController mock_manual_filling_controller_;

 private:
  std::unique_ptr<password_manager::ContentPasswordManagerDriver>
      password_manager_driver_;
  password_manager::StubPasswordManagerClient client_;
  TestingPrefServiceSimple test_pref_service_;
};

TEST_F(TouchToFillPasswordGenerationControllerTest,
       KeyboardIsSuppressedWhileTheBottomSheetIsShown) {
  auto bridge = std::make_unique<MockTouchToFillPasswordGenerationBridge>();
  MockTouchToFillPasswordGenerationBridge* bridge_ptr = bridge.get();
  auto controller = std::make_unique<TouchToFillPasswordGenerationController>(
      password_mananger_driver(), web_contents(),
      PasswordGenerationElementData(), std::move(bridge),
      on_dismissed_callback_.Get(),
      mock_manual_filling_controller_.AsWeakPtr());
  EXPECT_CALL(*bridge_ptr, Show);
  controller->ShowTouchToFill(
      test_user_account_, PasswordGenerationType::kAutomatic, pref_service());

  ui::mojom::TextInputStatePtr initial_state = ui::mojom::TextInputState::New();
  initial_state->type = ui::TEXT_INPUT_TYPE_PASSWORD;
  // Simulate the TextInputStateChanged call, which triggers the keyboard.
  SendTextInputStateChangedToWidget(rvh()->GetWidget(),
                                    std::move(initial_state));
  // Keyboard is expected to be suppressed.
  EXPECT_TRUE(content::GetTextInputStateFromWebContents(web_contents())
                  ->always_hide_ime);

  controller.reset();

  initial_state = ui::mojom::TextInputState::New();
  initial_state->type = ui::TEXT_INPUT_TYPE_PASSWORD;
  // Simulate the TextInputStateChanged call, which triggers the keyboard.
  SendTextInputStateChangedToWidget(rvh()->GetWidget(),
                                    std::move(initial_state));
  // Keyboard is expected to be shown again after resetting the controller.
  EXPECT_FALSE(content::GetTextInputStateFromWebContents(web_contents())
                   ->always_hide_ime);
}

TEST_F(TouchToFillPasswordGenerationControllerTest,
       OnDismissedCallbackIsTriggeredWhenBottomSheetDismissed) {
  auto controller = std::make_unique<TouchToFillPasswordGenerationController>(
      password_mananger_driver(), web_contents(),
      PasswordGenerationElementData(),
      std::make_unique<MockTouchToFillPasswordGenerationBridge>(),
      on_dismissed_callback_.Get(),
      mock_manual_filling_controller_.AsWeakPtr());

  controller->ShowTouchToFill(
      test_user_account_, PasswordGenerationType::kAutomatic, pref_service());

  EXPECT_CALL(on_dismissed_callback_, Run);
  controller->OnDismissed(/*generated_password_accepted=*/false);
}

TEST_F(TouchToFillPasswordGenerationControllerTest,
       CallsHideOnBridgeWhenTouchToFillPasswordGenerationControllerDestroyed) {
  auto bridge = std::make_unique<MockTouchToFillPasswordGenerationBridge>();
  MockTouchToFillPasswordGenerationBridge* bridge_ptr = bridge.get();
  auto controller = std::make_unique<TouchToFillPasswordGenerationController>(
      password_mananger_driver(), web_contents(),
      PasswordGenerationElementData(), std::move(bridge),
      on_dismissed_callback_.Get(),
      mock_manual_filling_controller_.AsWeakPtr());

  EXPECT_CALL(*bridge_ptr, Show(_, _, _, _, Eq(test_user_account_)));
  controller->ShowTouchToFill(
      test_user_account_, PasswordGenerationType::kAutomatic, pref_service());

  EXPECT_CALL(*bridge_ptr, Hide);
  controller.reset();
}

class TouchToFillPasswordGenerationControllerMetricsTest
    : public TouchToFillPasswordGenerationControllerTest,
      public testing::WithParamInterface<
          TouchToFillPasswordGenerationControllerMetricsParam> {};

TEST_P(TouchToFillPasswordGenerationControllerMetricsTest,
       MetricsReportedWhenBottomSheetIsDismissed) {
  auto [password_generation_type, generated_password_accepted,
        expected_histogram_name] = GetParam();
  base::HistogramTester histogram_tester;

  auto controller = std::make_unique<TouchToFillPasswordGenerationController>(
      password_mananger_driver(), web_contents(),
      PasswordGenerationElementData(),
      std::make_unique<MockTouchToFillPasswordGenerationBridge>(),
      on_dismissed_callback_.Get(),
      mock_manual_filling_controller_.AsWeakPtr());

  controller->ShowTouchToFill(test_user_account_, password_generation_type,
                              pref_service());

  controller->OnDismissed(generated_password_accepted);
  histogram_tester.ExpectUniqueSample(expected_histogram_name,
                                      generated_password_accepted
                                          ? GenerationDialogChoice::kAccepted
                                          : GenerationDialogChoice::kRejected,
                                      1);
}

INSTANTIATE_TEST_SUITE_P(
    All,
    TouchToFillPasswordGenerationControllerMetricsTest,
    testing::Values(
        TouchToFillPasswordGenerationControllerMetricsParam(
            PasswordGenerationType::kTouchToFill,
            /*generated_password_accepted=*/true,
            "PasswordManager.TouchToFill.PasswordGeneration.UserChoice"),
        TouchToFillPasswordGenerationControllerMetricsParam(
            PasswordGenerationType::kAutomatic,
            /*generated_password_accepted=*/false,
            "KeyboardAccessory.GenerationDialogChoice.Automatic"),
        TouchToFillPasswordGenerationControllerMetricsParam(
            PasswordGenerationType::kManual,
            /*generated_password_accepted=*/true,
            "KeyboardAccessory.GenerationDialogChoice.Manual")));