From 1fca20bf524134003013228aedf489ed6abec711 Mon Sep 17 00:00:00 2001 From: Michael Martis Date: Mon, 19 Dec 2022 01:12:48 +0000 Subject: [PATCH] [Live Caption] Tests for non-web prototype. This CL adds tests for new "non-web" audio source live caption functionality. The two CLs are kept separate to emphasize that this CL's broad footprint is trivial and for testing purposes only. We migrate to "for testing" helpers because adding more friend classes would violate layering (e.g. ::ash friends in ::content-level classes). Bug: b:253114860 Change-Id: Ib288aa8b6e5790565bc776e7e81d385eb4cc4dec Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4050680 Reviewed-by: Andrew Moylan Reviewed-by: Abigail Klein Reviewed-by: Katie Dektar Commit-Queue: Michael Martis Reviewed-by: Evan Liu Cr-Commit-Position: refs/heads/main@{#1084819} --- .../live_caption_controller_browsertest.cc | 7 +- ...ion_speech_recognition_host_browsertest.cc | 4 +- ...ion_unavailability_notifier_browsertest.cc | 4 +- .../system_live_caption_service.cc | 7 + .../system_live_caption_service.h | 15 + ...system_live_caption_service_browsertest.cc | 353 ++++++++++++++++++ ...peech_recognition_recognizer_client_impl.h | 10 +- ...tion_recognizer_client_impl_browsertest.cc | 2 +- chrome/test/BUILD.gn | 1 + .../live_caption/caption_bubble_controller.h | 7 +- .../live_caption/live_caption_controller.h | 9 +- .../live_caption/views/caption_bubble.cc | 4 + .../live_caption/views/caption_bubble.h | 1 + .../views/caption_bubble_controller_views.cc | 10 + .../views/caption_bubble_controller_views.h | 8 +- 15 files changed, 418 insertions(+), 24 deletions(-) create mode 100644 chrome/browser/ash/accessibility/system_live_caption_service_browsertest.cc diff --git a/chrome/browser/accessibility/live_caption_controller_browsertest.cc b/chrome/browser/accessibility/live_caption_controller_browsertest.cc index c026e3289ba18..d1513051d6fa2 100644 --- a/chrome/browser/accessibility/live_caption_controller_browsertest.cc +++ b/chrome/browser/accessibility/live_caption_controller_browsertest.cc @@ -73,7 +73,8 @@ class LiveCaptionControllerTest : public LiveCaptionBrowserTest { } CaptionBubbleController* GetBubbleControllerForProfile(Profile* profile) { - return GetControllerForProfile(profile)->caption_bubble_controller_.get(); + return GetControllerForProfile(profile) + ->caption_bubble_controller_for_testing(); } CaptionBubbleContextBrowser* GetCaptionBubbleContextBrowser() { @@ -123,8 +124,8 @@ class LiveCaptionControllerTest : public LiveCaptionBrowserTest { } bool HasBubbleControllerOnProfile(Profile* profile) { - return GetControllerForProfile(profile)->caption_bubble_controller_ != - nullptr; + return GetControllerForProfile(profile) + ->caption_bubble_controller_for_testing() != nullptr; } void ExpectIsWidgetVisible(bool visible) { diff --git a/chrome/browser/accessibility/live_caption_speech_recognition_host_browsertest.cc b/chrome/browser/accessibility/live_caption_speech_recognition_host_browsertest.cc index 15ac129b4479a..74163be0ef263 100644 --- a/chrome/browser/accessibility/live_caption_speech_recognition_host_browsertest.cc +++ b/chrome/browser/accessibility/live_caption_speech_recognition_host_browsertest.cc @@ -112,14 +112,14 @@ class LiveCaptionSpeechRecognitionHostTest : public LiveCaptionBrowserTest { bool HasBubbleController() { return LiveCaptionControllerFactory::GetForProfile(browser()->profile()) - ->caption_bubble_controller_.get() != nullptr; + ->caption_bubble_controller_for_testing() != nullptr; } void ExpectIsWidgetVisible(bool visible) { #if defined(TOOLKIT_VIEWS) CaptionBubbleController* bubble_controller = LiveCaptionControllerFactory::GetForProfile(browser()->profile()) - ->caption_bubble_controller_.get(); + ->caption_bubble_controller_for_testing(); EXPECT_EQ(visible, bubble_controller->IsWidgetVisibleForTesting()); #endif } diff --git a/chrome/browser/accessibility/live_caption_unavailability_notifier_browsertest.cc b/chrome/browser/accessibility/live_caption_unavailability_notifier_browsertest.cc index e861e202926af..39e5c209c1329 100644 --- a/chrome/browser/accessibility/live_caption_unavailability_notifier_browsertest.cc +++ b/chrome/browser/accessibility/live_caption_unavailability_notifier_browsertest.cc @@ -62,14 +62,14 @@ class LiveCaptionUnavailabilityNotifierTest : public LiveCaptionBrowserTest { bool HasBubbleController() { return LiveCaptionControllerFactory::GetForProfile(browser()->profile()) - ->caption_bubble_controller_.get() != nullptr; + ->caption_bubble_controller_for_testing() != nullptr; } void ExpectIsWidgetVisible(bool visible) { #if defined(TOOLKIT_VIEWS) CaptionBubbleController* bubble_controller = LiveCaptionControllerFactory::GetForProfile(browser()->profile()) - ->caption_bubble_controller_.get(); + ->caption_bubble_controller_for_testing(); EXPECT_EQ(visible, bubble_controller->IsWidgetVisibleForTesting()); #endif } diff --git a/chrome/browser/ash/accessibility/system_live_caption_service.cc b/chrome/browser/ash/accessibility/system_live_caption_service.cc index 8b253d338648c..1f040c901ae1b 100644 --- a/chrome/browser/ash/accessibility/system_live_caption_service.cc +++ b/chrome/browser/ash/accessibility/system_live_caption_service.cc @@ -100,6 +100,13 @@ void SystemLiveCaptionService::SpeechRecognitionAvailabilityChanged( prefs::GetLiveCaptionLanguageCode(profile_->GetPrefs()), /*is_server_based=*/false, media::mojom::RecognizerClientType::kLiveCaption)); + + // Inject a fake audio system in tests. + if (!create_audio_system_for_testing_.is_null()) { + client_->set_audio_system_for_testing( // IN-TEST + create_audio_system_for_testing_.Run()); + } + return; } diff --git a/chrome/browser/ash/accessibility/system_live_caption_service.h b/chrome/browser/ash/accessibility/system_live_caption_service.h index 46f8510ee7b43..c2013a74df4fb 100644 --- a/chrome/browser/ash/accessibility/system_live_caption_service.h +++ b/chrome/browser/ash/accessibility/system_live_caption_service.h @@ -27,6 +27,10 @@ namespace captions { class LiveCaptionController; } // namespace captions +namespace media { +class AudioSystem; +} // namespace media + namespace ash { // Responsible for running the live captioning model on audio from non-web (e.g. @@ -71,6 +75,13 @@ class SystemLiveCaptionService bool is_speech_recognition_available) override; void SpeechRecognitionLanguageChanged(const std::string& language) override; + void set_audio_system_factory_for_testing( + base::RepeatingCallback()> + create_audio_system_for_testing) { + create_audio_system_for_testing_ = + std::move(create_audio_system_for_testing); + } + private: // Stops and destructs audio stream recognizing client. void StopRecognizing(); @@ -85,6 +96,10 @@ class SystemLiveCaptionService mojo::Receiver browser_observer_receiver_{this}; + // Used to inject a fake audio system into our client in tests. + base::RepeatingCallback()> + create_audio_system_for_testing_; + base::WeakPtrFactory weak_ptr_factory_{this}; }; diff --git a/chrome/browser/ash/accessibility/system_live_caption_service_browsertest.cc b/chrome/browser/ash/accessibility/system_live_caption_service_browsertest.cc new file mode 100644 index 0000000000000..d06abe297f6fa --- /dev/null +++ b/chrome/browser/ash/accessibility/system_live_caption_service_browsertest.cc @@ -0,0 +1,353 @@ +// Copyright 2022 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/ash/accessibility/system_live_caption_service.h" + +#include + +#include "ash/constants/ash_features.h" +#include "ash/constants/ash_switches.h" +#include "chrome/browser/accessibility/live_caption_controller_factory.h" +#include "chrome/browser/ash/accessibility/system_live_caption_service_factory.h" +#include "chrome/browser/ash/login/session/user_session_initializer.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/profiles/profile_test_util.h" +#include "chrome/browser/speech/cros_speech_recognition_service_factory.h" +#include "chrome/browser/speech/fake_speech_recognition_service.h" +#include "chrome/browser/speech/speech_recognizer_delegate.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "components/live_caption/caption_bubble_controller.h" +#include "components/live_caption/live_caption_controller.h" +#include "components/live_caption/pref_names.h" +#include "components/soda/constants.h" +#include "components/soda/soda_installer.h" +#include "content/public/test/browser_test.h" +#include "media/audio/audio_system.h" +#include "media/base/audio_parameters.h" +#include "media/mojo/mojom/speech_recognition_service.mojom.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; + +namespace ash { + +namespace { +static constexpr int kDefaultSampleRateMs = 16000; +static constexpr int kDefaultPollingTimesHz = 10; +} // namespace + +// We need to swap out the device audio system for a fake one. +class MockAudioSystem : public media::AudioSystem { + public: + MockAudioSystem() = default; + ~MockAudioSystem() override = default; + MockAudioSystem(const MockAudioSystem&) = delete; + MockAudioSystem& operator=(const MockAudioSystem&) = delete; + + // media::AudioSystem overrides: + MOCK_METHOD2(GetInputStreamParameters, + void(const std::string& device_id, + OnAudioParamsCallback callback)); + MOCK_METHOD2(GetOutputStreamParameters, + void(const std::string& device_id, + OnAudioParamsCallback on_params_cb)); + MOCK_METHOD1(HasInputDevices, void(OnBoolCallback on_has_devices_cb)); + MOCK_METHOD1(HasOutputDevices, void(OnBoolCallback on_has_devices_cb)); + MOCK_METHOD2(GetDeviceDescriptions, + void(bool for_input, + OnDeviceDescriptionsCallback on_descriptions_cp)); + MOCK_METHOD2(GetAssociatedOutputDeviceID, + void(const std::string& input_device_id, + OnDeviceIdCallback on_device_id_cb)); + MOCK_METHOD2(GetInputDeviceInfo, + void(const std::string& input_device_id, + OnInputDeviceInfoCallback on_input_device_info_cb)); +}; + +// Creates and returns a stub audio system that reports a reasonable default for +// audio device parameters. +std::unique_ptr CreateStubAudioSystem() { + const media::AudioParameters params( + media::AudioParameters::AUDIO_PCM_LOW_LATENCY, + media::ChannelLayoutConfig::Stereo(), kDefaultSampleRateMs, + kDefaultSampleRateMs / (kDefaultPollingTimesHz * 2)); + + std::unique_ptr stub_audio_system = + std::make_unique(); + EXPECT_CALL(*stub_audio_system, GetInputStreamParameters(_, _)) + .WillRepeatedly( + [params](auto, MockAudioSystem::OnAudioParamsCallback cb) { + std::move(cb).Run(params); + }); + + return stub_audio_system; +} + +// Runs the system live caption service backed by a fake audio system and SODA +// installation. +class SystemLiveCaptionServiceTest : public InProcessBrowserTest { + public: + SystemLiveCaptionServiceTest() { + scoped_feature_list_.InitWithFeatures( + /*enabled_features=*/{features::kOnDeviceSpeechRecognition, + features::kSystemLiveCaption}, + /*disabled_features=*/{}); + } + + ~SystemLiveCaptionServiceTest() override = default; + SystemLiveCaptionServiceTest(const SystemLiveCaptionServiceTest&) = delete; + SystemLiveCaptionServiceTest& operator=(const SystemLiveCaptionServiceTest&) = + delete; + + void SetUpCommandLine(base::CommandLine* command_line) override { + command_line->AppendSwitch(switches::kIgnoreUserProfileMappingForTests); + } + + void SetUpOnMainThread() override { + InProcessBrowserTest::SetUpOnMainThread(); + primary_profile_ = browser()->profile(); + + // Create an additional profile. We will verify that its caption bubble is + // inactive, since only the primary profile should be processing system + // audio. + ProfileManager* profile_manager = g_browser_process->profile_manager(); + const base::FilePath profile_path = + profile_manager->GenerateNextProfileDirectoryPath(); + secondary_profile_ = + profiles::testing::CreateProfileSync(profile_manager, profile_path); + CHECK(secondary_profile_); + + // Replace our CrosSpeechRecognitionService with a fake one. We can pass a + // unique_ptr into this lambda since it is only called once (despite being + // "repeating"). + auto service = std::make_unique(); + fake_speech_recognition_service_ = service.get(); + const auto spawn_test_service = + base::BindRepeating([](std::unique_ptr s, + content::BrowserContext*) { return s; }, + base::Passed(std::move(service))); + CrosSpeechRecognitionServiceFactory::GetInstanceForTest() + ->SetTestingFactoryAndUse(primary_profile_, spawn_test_service); + + // Pass in an inert audio system backend. + SystemLiveCaptionServiceFactory::GetInstance() + ->GetForProfile(primary_profile_) + ->set_audio_system_factory_for_testing( + base::BindRepeating(&CreateStubAudioSystem)); + + // Don't actually try to download SODA. + speech::SodaInstaller::GetInstance()->NeverDownloadSodaForTesting(); + + // Use English as our caption language. + primary_profile_->GetPrefs()->SetString(prefs::kLiveCaptionLanguageCode, + speech::kUsEnglishLocale); + } + + ::captions::CaptionBubbleController* GetCaptionBubbleController( + Profile* profile) const { + return ::captions::LiveCaptionControllerFactory::GetInstance() + ->GetForProfile(profile) + ->caption_bubble_controller_for_testing(); + } + + void SetLiveCaptionsPref(Profile* profile, bool enabled) { + primary_profile_->GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled, + enabled); + base::RunLoop().RunUntilIdle(); + } + + // Emit the given text from our fake speech recognition service. + void EmulateRecognizedSpeech(const std::string& text) { + fake_speech_recognition_service_->SendSpeechRecognitionResult( + media::SpeechRecognitionResult(text, /*is_final=*/false)); + base::RunLoop().RunUntilIdle(); + } + + // Meet the preconditions for live captioning so that our logic-under-test + // starts executing. + void StartLiveCaptioning() { + SetLiveCaptionsPref(primary_profile_, /*enabled=*/true); + speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting( + speech::LanguageCode::kEnUs); + speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(); + base::RunLoop().RunUntilIdle(); + } + + // Unowned. + Profile* primary_profile_; + Profile* secondary_profile_; + speech::FakeSpeechRecognitionService* fake_speech_recognition_service_; + + base::test::ScopedFeatureList scoped_feature_list_; +}; + +// Tests that system audio is processed only when all our preconditions are +// satisfied. +IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, Triggering) { + // We should be waiting for the feature to be enabled and for SODA to be + // installed. + EXPECT_FALSE(fake_speech_recognition_service_->is_capturing_audio()); + + // Enable feature. + SetLiveCaptionsPref(primary_profile_, /*enabled=*/true); + + // We should still be waiting for SODA to be installed. + EXPECT_FALSE(fake_speech_recognition_service_->is_capturing_audio()); + + // Fake successful language pack install. + speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting( + speech::LanguageCode::kEnUs); + base::RunLoop().RunUntilIdle(); + + // We should be waiting for the base binary too. + EXPECT_FALSE(fake_speech_recognition_service_->is_capturing_audio()); + + // Fake successful base binary install. + speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(); + base::RunLoop().RunUntilIdle(); + + // Should now be processing system audio. + EXPECT_TRUE(fake_speech_recognition_service_->is_capturing_audio()); + + // Now turn off live captioning. + SetLiveCaptionsPref(primary_profile_, /*enabled=*/false); + + // This should stop audio fetching. + EXPECT_FALSE(fake_speech_recognition_service_->is_capturing_audio()); +} + +// Test that feature is gated on successful SODA install. +IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, SodaError) { + // Enable feature so that we start listening for SODA install status. + SetLiveCaptionsPref(primary_profile_, /*enabled=*/true); + + // Fake successful base binary install but failed language install. + speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(); + speech::SodaInstaller::GetInstance()->NotifySodaErrorForTesting( + speech::LanguageCode::kEnUs); + base::RunLoop().RunUntilIdle(); + + // Our language is not yet installed, so we shouldn't be processing audio. + EXPECT_FALSE(fake_speech_recognition_service_->is_capturing_audio()); +} + +// Tests that our feature listens to the correct SODA language. +IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, SodaIrrelevantError) { + // Enable feature so that we start listening for SODA install status. + SetLiveCaptionsPref(primary_profile_, /*enabled=*/true); + + // Fake successful base binary install. + speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(); + base::RunLoop().RunUntilIdle(); + + // Fake failed install of an unrelated language. + speech::SodaInstaller::GetInstance()->NotifySodaErrorForTesting( + speech::LanguageCode::kFrFr); + base::RunLoop().RunUntilIdle(); + + // Our language is not yet installed, so we shouldn't be processing audio. + EXPECT_FALSE(fake_speech_recognition_service_->is_capturing_audio()); + + // Fake successful install of our language. + speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting( + speech::LanguageCode::kEnUs); + base::RunLoop().RunUntilIdle(); + + // We should have ignored the unrelated error. + EXPECT_TRUE(fake_speech_recognition_service_->is_capturing_audio()); +} + +// Test that captions are only dispatched for the primary profile. +IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, DispatchToProfile) { + StartLiveCaptioning(); + + // Capture fake audio. + EmulateRecognizedSpeech("System audio caption"); + EXPECT_TRUE(fake_speech_recognition_service_->is_capturing_audio()); + + // Transcribed speech should be displayed from the primary profile. + auto* primary_bubble = GetCaptionBubbleController(primary_profile_); + ASSERT_NE(nullptr, primary_bubble); + EXPECT_TRUE(primary_bubble->IsWidgetVisibleForTesting()); + EXPECT_FALSE(primary_bubble->IsGenericErrorMessageVisibleForTesting()); + EXPECT_EQ("System audio caption", + primary_bubble->GetBubbleLabelTextForTesting()); + + // Transcribed speech should _not_ be shown for any other profiles. + EXPECT_EQ(nullptr, GetCaptionBubbleController(secondary_profile_)); +} + +// Test that we can cease transcription by closing the bubble UI. +IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, EarlyStopping) { + StartLiveCaptioning(); + + // Fake some speech. + EmulateRecognizedSpeech("System audio caption"); + + // Bubble UI should be active to show transcribed speech. + auto* primary_bubble = GetCaptionBubbleController(primary_profile_); + ASSERT_NE(nullptr, primary_bubble); + + // Emulate closing bubble UI. + primary_bubble->CloseActiveModelForTesting(); + + // Fake detection of more speech, to which the bubble should respond by + // requesting an early stop. + EmulateRecognizedSpeech("More system audio captions"); + + // The speech recognition service should have received the early stop request. + EXPECT_FALSE(fake_speech_recognition_service_->is_capturing_audio()); +} + +// Test that the UI is closed when transcription is complete. +IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, EndOfStream) { + StartLiveCaptioning(); + + // Fake some speech. + EmulateRecognizedSpeech("System audio caption"); + + // Bubble UI should be active to show transcribed speech. + auto* primary_bubble = GetCaptionBubbleController(primary_profile_); + ASSERT_NE(nullptr, primary_bubble); + EXPECT_TRUE(primary_bubble->IsWidgetVisibleForTesting()); + + // Emulate end of audio stream. + fake_speech_recognition_service_->MarkDone(); + base::RunLoop().RunUntilIdle(); + + // Bubble should not be shown since there is no more audio. + primary_bubble = GetCaptionBubbleController(primary_profile_); + ASSERT_NE(nullptr, primary_bubble); + EXPECT_FALSE(primary_bubble->IsWidgetVisibleForTesting()); +} + +// Test that an error message is shown if something goes wrong. +IN_PROC_BROWSER_TEST_F(SystemLiveCaptionServiceTest, ServiceError) { + StartLiveCaptioning(); + + // Fake some speech. + EmulateRecognizedSpeech("System audio caption"); + + // Bubble UI should be active to show transcribed speech. + auto* primary_bubble = GetCaptionBubbleController(primary_profile_); + ASSERT_NE(nullptr, primary_bubble); + EXPECT_TRUE(primary_bubble->IsWidgetVisibleForTesting()); + EXPECT_FALSE(primary_bubble->IsGenericErrorMessageVisibleForTesting()); + + // Emulate recognition error. + fake_speech_recognition_service_->SendSpeechRecognitionError(); + base::RunLoop().RunUntilIdle(); + + // Bubble should still be shown and should display error text. + primary_bubble = GetCaptionBubbleController(primary_profile_); + ASSERT_NE(nullptr, primary_bubble); + EXPECT_TRUE(primary_bubble->IsWidgetVisibleForTesting()); + EXPECT_TRUE(primary_bubble->IsGenericErrorMessageVisibleForTesting()); +} + +} // namespace ash diff --git a/chrome/browser/speech/speech_recognition_recognizer_client_impl.h b/chrome/browser/speech/speech_recognition_recognizer_client_impl.h index 865ad4d00a6b9..afb195023acdd 100644 --- a/chrome/browser/speech/speech_recognition_recognizer_client_impl.h +++ b/chrome/browser/speech/speech_recognition_recognizer_client_impl.h @@ -12,6 +12,7 @@ #include "base/memory/weak_ptr.h" #include "chrome/browser/speech/speech_recognizer.h" #include "chrome/browser/speech/speech_recognizer_delegate.h" +#include "media/audio/audio_system.h" #include "media/mojo/mojom/speech_recognition_service.mojom.h" #include "mojo/public/cpp/bindings/receiver.h" #include "mojo/public/cpp/bindings/remote.h" @@ -64,9 +65,12 @@ class SpeechRecognitionRecognizerClientImpl media::mojom::LanguageIdentificationEventPtr event) override; void OnSpeechRecognitionStopped() override; - private: - friend class SpeechRecognitionRecognizerClientImplTest; + void set_audio_system_for_testing( + std::unique_ptr audio_system) { + audio_system_ = std::move(audio_system); + } + private: void OnRecognizerBound(bool success); void OnRecognizerDisconnected(); void StartFetchingOnInputDeviceInfo( @@ -85,7 +89,7 @@ class SpeechRecognitionRecognizerClientImpl // in between requesting the callback and it running. bool waiting_for_params_; - // Tests may set audio_system_ after constructing an + // Tests may use the audio system setter above after constructing an // SpeechRecognitionRecognizerClientImpl to override default behavior. std::unique_ptr audio_system_; diff --git a/chrome/browser/speech/speech_recognition_recognizer_client_impl_browsertest.cc b/chrome/browser/speech/speech_recognition_recognizer_client_impl_browsertest.cc index 8682eebc25799..979b812afb786 100644 --- a/chrome/browser/speech/speech_recognition_recognizer_client_impl_browsertest.cc +++ b/chrome/browser/speech/speech_recognition_recognizer_client_impl_browsertest.cc @@ -168,7 +168,7 @@ class SpeechRecognitionRecognizerClientImplTest : public InProcessBrowserTest { std::make_unique(); mock_audio_system->SetInputStreamParameters( media::AudioDeviceDescription::kDefaultDeviceId, params); - recognizer_->audio_system_ = std::move(mock_audio_system); + recognizer_->set_audio_system_for_testing(std::move(mock_audio_system)); StartAndWaitForRecognizing(); } diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 6322de08c60fe..3590b80995558 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn @@ -3662,6 +3662,7 @@ if (!is_android) { "../browser/ash/accessibility/spoken_feedback_browsertest.h", "../browser/ash/accessibility/sticky_keys_browsertest.cc", "../browser/ash/accessibility/switch_access_browsertest.cc", + "../browser/ash/accessibility/system_live_caption_service_browsertest.cc", "../browser/ash/accessibility/touch_exploration_controller_browsertest.cc", "../browser/ash/account_manager/account_manager_policy_controller_browsertest.cc", "../browser/ash/app_list/app_list_client_impl_browsertest.cc", diff --git a/components/live_caption/caption_bubble_controller.h b/components/live_caption/caption_bubble_controller.h index 4d9ba3d200429..c93f88ca42db9 100644 --- a/components/live_caption/caption_bubble_controller.h +++ b/components/live_caption/caption_bubble_controller.h @@ -64,13 +64,10 @@ class CaptionBubbleController { virtual void UpdateCaptionStyle( absl::optional caption_style) = 0; - private: - friend class LiveCaptionControllerTest; - friend class LiveCaptionSpeechRecognitionHostTest; - friend class LiveCaptionUnavailabilityNotifierTest; - virtual bool IsWidgetVisibleForTesting() = 0; + virtual bool IsGenericErrorMessageVisibleForTesting() = 0; virtual std::string GetBubbleLabelTextForTesting() = 0; + virtual void CloseActiveModelForTesting() = 0; }; } // namespace captions diff --git a/components/live_caption/live_caption_controller.h b/components/live_caption/live_caption_controller.h index 422c2bf3e8daf..f91f8a604735c 100644 --- a/components/live_caption/live_caption_controller.h +++ b/components/live_caption/live_caption_controller.h @@ -80,12 +80,11 @@ class LiveCaptionController : public KeyedService, void OnToggleFullscreen(CaptionBubbleContext* caption_bubble_context); #endif - private: - friend class LiveCaptionControllerFactory; - friend class LiveCaptionControllerTest; - friend class LiveCaptionSpeechRecognitionHostTest; - friend class LiveCaptionUnavailabilityNotifierTest; + CaptionBubbleController* caption_bubble_controller_for_testing() { + return caption_bubble_controller_.get(); + } + private: // SodaInstaller::Observer: void OnSodaInstalled(speech::LanguageCode language_code) override; void OnSodaProgress(speech::LanguageCode language_code, diff --git a/components/live_caption/views/caption_bubble.cc b/components/live_caption/views/caption_bubble.cc index 3bfab0918e62c..13e158b8e98d8 100644 --- a/components/live_caption/views/caption_bubble.cc +++ b/components/live_caption/views/caption_bubble.cc @@ -1159,6 +1159,10 @@ views::Label* CaptionBubble::GetLabelForTesting() { return static_cast(label_); } +bool CaptionBubble::IsGenericErrorMessageVisibleForTesting() const { + return generic_error_message_->GetVisible(); +} + void CaptionBubble::SetCaptionBubbleStyle() { SetTextSizeAndFontFamily(); if (GetWidget()) { diff --git a/components/live_caption/views/caption_bubble.h b/components/live_caption/views/caption_bubble.h index d3008d7523613..6bcbb195ce9fe 100644 --- a/components/live_caption/views/caption_bubble.h +++ b/components/live_caption/views/caption_bubble.h @@ -92,6 +92,7 @@ class CaptionBubble : public views::BubbleDialogDelegateView { bool HasActivity(); views::Label* GetLabelForTesting(); + bool IsGenericErrorMessageVisibleForTesting() const; base::RetainingOneShotTimer* GetInactivityTimerForTesting(); void set_tick_clock_for_testing(const base::TickClock* tick_clock) { tick_clock_ = tick_clock; diff --git a/components/live_caption/views/caption_bubble_controller_views.cc b/components/live_caption/views/caption_bubble_controller_views.cc index 5dacd0f66b4f8..7a4e738640d50 100644 --- a/components/live_caption/views/caption_bubble_controller_views.cc +++ b/components/live_caption/views/caption_bubble_controller_views.cc @@ -169,6 +169,11 @@ bool CaptionBubbleControllerViews::IsWidgetVisibleForTesting() { return caption_widget_ && caption_widget_->IsVisible(); } +bool CaptionBubbleControllerViews::IsGenericErrorMessageVisibleForTesting() { + return caption_bubble_ && + caption_bubble_->IsGenericErrorMessageVisibleForTesting(); // IN-TEST +} + std::string CaptionBubbleControllerViews::GetBubbleLabelTextForTesting() { return caption_bubble_ ? base::UTF16ToUTF8( @@ -176,4 +181,9 @@ std::string CaptionBubbleControllerViews::GetBubbleLabelTextForTesting() { : ""; } +void CaptionBubbleControllerViews::CloseActiveModelForTesting() { + if (active_model_) + active_model_->Close(); +} + } // namespace captions diff --git a/components/live_caption/views/caption_bubble_controller_views.h b/components/live_caption/views/caption_bubble_controller_views.h index 5dbc46732b7c7..ad24fcb4d4996 100644 --- a/components/live_caption/views/caption_bubble_controller_views.h +++ b/components/live_caption/views/caption_bubble_controller_views.h @@ -60,6 +60,11 @@ class CaptionBubbleControllerViews : public CaptionBubbleController { void UpdateCaptionStyle( absl::optional caption_style) override; + bool IsWidgetVisibleForTesting() override; + bool IsGenericErrorMessageVisibleForTesting() override; + std::string GetBubbleLabelTextForTesting() override; + void CloseActiveModelForTesting() override; + private: friend class CaptionBubbleControllerViewsTest; friend class LiveCaptionUnavailabilityNotifierTest; @@ -79,9 +84,6 @@ class CaptionBubbleControllerViews : public CaptionBubbleController { // Called on a cross-origin navigation or reload. void OnSessionReset(const std::string& session_id); - bool IsWidgetVisibleForTesting() override; - std::string GetBubbleLabelTextForTesting() override; - raw_ptr caption_bubble_; raw_ptr caption_widget_;