blob: 520e3af2f6fa84d1d29a2563dddd564f692fc074 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/live_caption/live_caption_controller.h"
#include <ranges>
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/callback_forward.h"
#include "build/build_config.h"
#include "chrome/browser/accessibility/caption_bubble_context_browser.h"
#include "chrome/browser/accessibility/live_caption/live_caption_controller_factory.h"
#include "chrome/browser/accessibility/live_caption/live_caption_test_util.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.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/profiles/profile_window.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/live_caption/caption_bubble_controller.h"
#include "components/live_caption/pref_names.h"
#include "components/soda/constants.h"
#include "components/soda/pref_names.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "content/public/test/browser_test.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_features.h"
#endif
namespace captions {
namespace {
speech::LanguageCode en_us() {
return speech::LanguageCode::kEnUs;
}
speech::LanguageCode fr_fr() {
return speech::LanguageCode::kFrFr;
}
} // namespace
Profile* CreateProfile() {
ProfileManager* profile_manager = g_browser_process->profile_manager();
base::FilePath profile_path =
profile_manager->GenerateNextProfileDirectoryPath();
return &profiles::testing::CreateProfileSync(profile_manager, profile_path);
}
class LiveCaptionControllerTest : public LiveCaptionBrowserTest {
public:
LiveCaptionControllerTest() = default;
~LiveCaptionControllerTest() override = default;
LiveCaptionControllerTest(const LiveCaptionControllerTest&) = delete;
LiveCaptionControllerTest& operator=(const LiveCaptionControllerTest&) =
delete;
LiveCaptionController* GetControllerForProfile(Profile* profile) {
return LiveCaptionControllerFactory::GetForProfile(profile);
}
CaptionBubbleController* GetBubbleController() {
return GetBubbleControllerForProfile(browser()->profile());
}
CaptionBubbleController* GetBubbleControllerForProfile(Profile* profile) {
return GetControllerForProfile(profile)
->caption_bubble_controller_for_testing();
}
CaptionBubbleContextBrowser* GetCaptionBubbleContextBrowser() {
if (!caption_bubble_context_) {
caption_bubble_context_ = CaptionBubbleContextBrowser::Create(
browser()->tab_strip_model()->GetActiveWebContents());
}
return caption_bubble_context_.get();
}
bool DispatchTranscription(std::string text) {
return DispatchTranscriptionToProfile(text, browser()->profile());
}
bool DispatchTranscriptionToProfile(std::string text, Profile* profile) {
return GetControllerForProfile(profile)->DispatchTranscription(
browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame(),
GetCaptionBubbleContextBrowser(),
media::SpeechRecognitionResult(text, /* is_final */ false));
}
void OnError() { OnErrorOnProfile(browser()->profile()); }
void OnErrorOnProfile(Profile* profile) {
GetControllerForProfile(profile)->OnError(
GetCaptionBubbleContextBrowser(), CaptionBubbleErrorType::kGeneric,
base::RepeatingClosure(),
base::BindRepeating(
[](CaptionBubbleErrorType error_type, bool checked) {}));
}
void OnAudioStreamEnd() { OnAudioStreamEndOnProfile(browser()->profile()); }
void OnAudioStreamEndOnProfile(Profile* profile) {
GetControllerForProfile(profile)->OnAudioStreamEnd(
browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame(),
GetCaptionBubbleContextBrowser());
}
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS)
void OnToggleFullscreen() {
GetControllerForProfile(browser()->profile())
->OnToggleFullscreen(GetCaptionBubbleContextBrowser());
}
#endif
bool HasBubbleController() {
return HasBubbleControllerOnProfile(browser()->profile());
}
bool HasBubbleControllerOnProfile(Profile* profile) {
return GetControllerForProfile(profile)
->caption_bubble_controller_for_testing() != nullptr;
}
void ExpectIsWidgetVisible(bool visible) {
ExpectIsWidgetVisibleOnProfile(visible, browser()->profile());
}
void ExpectIsWidgetVisibleOnProfile(bool visible, Profile* profile) {
#if defined(TOOLKIT_VIEWS)
EXPECT_EQ(
visible,
GetBubbleControllerForProfile(profile)->IsWidgetVisibleForTesting());
#endif
}
void ExpectBubbleLabelTextEquals(std::string text) {
ExpectBubbleLabelTextOnProfileEquals(text, browser()->profile());
}
void ExpectBubbleLabelTextOnProfileEquals(std::string text,
Profile* profile) {
#if defined(TOOLKIT_VIEWS)
EXPECT_EQ(
text,
GetBubbleControllerForProfile(profile)->GetBubbleLabelTextForTesting());
#endif
}
private:
std::unique_ptr<CaptionBubbleContextBrowser> caption_bubble_context_;
};
IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, ProfilePrefsAreRegistered) {
EXPECT_FALSE(
browser()->profile()->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled));
#if !BUILDFLAG(IS_CHROMEOS)
// These prefs are used for the component updater, but SODA does not use the
// component updater on Chrome OS.
EXPECT_EQ(base::FilePath(), g_browser_process->local_state()->GetFilePath(
prefs::kSodaBinaryPath));
EXPECT_EQ(base::FilePath(), g_browser_process->local_state()->GetFilePath(
prefs::kSodaEnUsConfigPath));
#endif // !BUILDFLAG(IS_CHROMEOS)
}
IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest,
ProfilePrefsAreRegistered_Incognito) {
// Set live caption enabled on the regular profile.
SetLiveCaptionEnabled(true);
EXPECT_TRUE(
browser()->profile()->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled));
#if !BUILDFLAG(IS_CHROMEOS)
// These prefs are used for the component updater, but SODA does not use the
// component updater on Chrome OS.
EXPECT_EQ(base::FilePath(), g_browser_process->local_state()->GetFilePath(
prefs::kSodaBinaryPath));
EXPECT_EQ(base::FilePath(), g_browser_process->local_state()->GetFilePath(
prefs::kSodaEnUsConfigPath));
#endif // !BUILDFLAG(IS_CHROMEOS)
// Ensure that live caption is also enabled in the incognito profile.
Profile* incognito_profile =
browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
EXPECT_TRUE(
incognito_profile->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled));
#if !BUILDFLAG(IS_CHROMEOS)
// These prefs are used for the component updater, but SODA does not use the
// component updater on Chrome OS.
EXPECT_EQ(base::FilePath(), g_browser_process->local_state()->GetFilePath(
prefs::kSodaBinaryPath));
EXPECT_EQ(base::FilePath(), g_browser_process->local_state()->GetFilePath(
prefs::kSodaEnUsConfigPath));
#endif // !BUILDFLAG(IS_CHROMEOS)
}
IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, LiveCaptionEnabledChanged) {
EXPECT_EQ(nullptr, GetBubbleController());
EXPECT_FALSE(HasBubbleController());
SetLiveCaptionEnabled(true);
EXPECT_NE(nullptr, GetBubbleController());
EXPECT_TRUE(HasBubbleController());
SetLiveCaptionEnabled(false);
EXPECT_EQ(nullptr, GetBubbleController());
EXPECT_FALSE(HasBubbleController());
}
IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest,
LiveCaptionEnabledChanged_BubbleVisible) {
SetLiveCaptionEnabled(true);
// Make the bubble visible by dispatching a transcription.
DispatchTranscription(
"In Switzerland it is illegal to own just one guinea pig.");
ExpectIsWidgetVisible(true);
SetLiveCaptionEnabled(false);
EXPECT_EQ(nullptr, GetBubbleController());
EXPECT_FALSE(HasBubbleController());
}
IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, OnSodaInstalled) {
EXPECT_FALSE(HasBubbleController());
browser()->profile()->GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled,
true);
// The UI must be created once SODA is installed, but possibly earlier.
speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(en_us());
speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
EXPECT_TRUE(HasBubbleController());
}
// TODO(crbug.com/40936746): Re-enable this test.
IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, DISABLED_OnSodaError) {
// Live Caption is disabled when there is an error in the SODA download for
// the language belonging to Live Caption.
browser()->profile()->GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled,
true);
EXPECT_TRUE(
browser()->profile()->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled));
speech::SodaInstaller::GetInstance()->NotifySodaErrorForTesting(en_us());
EXPECT_FALSE(
browser()->profile()->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled));
// Live Caption is disabled when there is an error in the SODA binary
// download.
browser()->profile()->GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled,
true);
EXPECT_TRUE(
browser()->profile()->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled));
speech::SodaInstaller::GetInstance()->NotifySodaErrorForTesting();
EXPECT_FALSE(
browser()->profile()->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled));
// Live Caption is not disabled when there is an error in the SODA download
// for a language not belonging to Live Caption.
browser()->profile()->GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled,
true);
EXPECT_TRUE(
browser()->profile()->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled));
speech::SodaInstaller::GetInstance()->NotifySodaErrorForTesting(fr_fr());
EXPECT_TRUE(
browser()->profile()->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled));
}
IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, DispatchTranscription) {
bool success = DispatchTranscription("A baby spider is called a spiderling.");
EXPECT_FALSE(success);
EXPECT_FALSE(HasBubbleController());
SetLiveCaptionEnabled(true);
success = DispatchTranscription(
"A baby octopus is about the size of a flea when it is born.");
EXPECT_TRUE(success);
ExpectIsWidgetVisible(true);
ExpectBubbleLabelTextEquals(
"A baby octopus is about the size of a flea when it is born.");
SetLiveCaptionEnabled(false);
success = DispatchTranscription(
"Approximately 10-20% of power outages in the US are caused by "
"squirrels.");
EXPECT_FALSE(success);
EXPECT_FALSE(HasBubbleController());
}
IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, OnError) {
EXPECT_FALSE(HasBubbleController());
OnError();
EXPECT_TRUE(HasBubbleController());
SetLiveCaptionEnabled(true);
OnError();
ExpectIsWidgetVisible(true);
SetLiveCaptionEnabled(false);
OnError();
EXPECT_TRUE(HasBubbleController());
}
IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, OnAudioStreamEnd) {
OnAudioStreamEnd();
EXPECT_FALSE(HasBubbleController());
SetLiveCaptionEnabled(true);
DispatchTranscription("Some cicadas appear only once every 17 years.");
ExpectIsWidgetVisible(true);
OnAudioStreamEnd();
ExpectIsWidgetVisible(false);
SetLiveCaptionEnabled(false);
OnAudioStreamEnd();
EXPECT_FALSE(HasBubbleController());
}
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, OnToggleFullscreen) {
OnToggleFullscreen();
EXPECT_FALSE(HasBubbleController());
SetLiveCaptionEnabled(true);
EXPECT_TRUE(HasBubbleController());
OnToggleFullscreen();
EXPECT_TRUE(HasBubbleController());
SetLiveCaptionEnabled(false);
OnToggleFullscreen();
EXPECT_FALSE(HasBubbleController());
}
#endif
#if !BUILDFLAG(IS_CHROMEOS) // No multi-profile on ChromeOS.
IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest,
LiveCaptionEnabledChanged_MultipleProfiles) {
Profile* profile1 = browser()->profile();
Profile* profile2 = CreateProfile();
// The profiles start with no caption bubble controllers.
EXPECT_FALSE(HasBubbleControllerOnProfile(profile1));
EXPECT_FALSE(HasBubbleControllerOnProfile(profile2));
// Enable live caption on profile1.
SetLiveCaptionEnabled(true);
EXPECT_TRUE(HasBubbleControllerOnProfile(profile1));
EXPECT_FALSE(HasBubbleControllerOnProfile(profile2));
// Enable live caption on profile2.
SetLiveCaptionEnabledOnProfile(true, profile2);
EXPECT_TRUE(HasBubbleControllerOnProfile(profile1));
EXPECT_TRUE(HasBubbleControllerOnProfile(profile2));
// Disable live caption on profile1.
SetLiveCaptionEnabled(false);
EXPECT_FALSE(HasBubbleControllerOnProfile(profile1));
EXPECT_TRUE(HasBubbleControllerOnProfile(profile2));
// Disable live caption on profile2.
SetLiveCaptionEnabledOnProfile(false, profile2);
EXPECT_FALSE(HasBubbleControllerOnProfile(profile1));
EXPECT_FALSE(HasBubbleControllerOnProfile(profile2));
}
IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest,
DispatchTranscription_MultipleProfiles) {
Profile* profile1 = browser()->profile();
Profile* profile2 = CreateProfile();
// Enable live caption on both profiles.
SetLiveCaptionEnabled(true);
SetLiveCaptionEnabledOnProfile(true, profile2);
// Dispatch transcription routes the transcription to the right profile.
bool success =
DispatchTranscriptionToProfile("Only female mosquitos bite.", profile1);
EXPECT_TRUE(success);
ExpectIsWidgetVisibleOnProfile(true, profile1);
ExpectIsWidgetVisibleOnProfile(false, profile2);
ExpectBubbleLabelTextOnProfileEquals("Only female mosquitos bite.", profile1);
ExpectBubbleLabelTextOnProfileEquals("", profile2);
success = DispatchTranscriptionToProfile(
"Mosquitos were around at the time of the dinosaurs.", profile2);
EXPECT_TRUE(success);
ExpectIsWidgetVisibleOnProfile(true, profile1);
ExpectIsWidgetVisibleOnProfile(true, profile2);
ExpectBubbleLabelTextOnProfileEquals("Only female mosquitos bite.", profile1);
ExpectBubbleLabelTextOnProfileEquals(
"Mosquitos were around at the time of the dinosaurs.", profile2);
}
IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, OnError_MultipleProfiles) {
Profile* profile1 = browser()->profile();
Profile* profile2 = CreateProfile();
// Enable live caption on both profiles.
SetLiveCaptionEnabled(true);
SetLiveCaptionEnabledOnProfile(true, profile2);
// OnError routes to the right profile.
OnErrorOnProfile(profile1);
ExpectIsWidgetVisibleOnProfile(true, profile1);
ExpectIsWidgetVisibleOnProfile(false, profile2);
OnErrorOnProfile(profile2);
ExpectIsWidgetVisibleOnProfile(true, profile1);
ExpectIsWidgetVisibleOnProfile(true, profile2);
}
IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest,
OnAudioStreamEnd_MultipleProfiles) {
Profile* profile1 = browser()->profile();
Profile* profile2 = CreateProfile();
// Enable live caption on both profiles.
SetLiveCaptionEnabled(true);
SetLiveCaptionEnabledOnProfile(true, profile2);
DispatchTranscriptionToProfile(
"Capybaras are the largest rodents in the world.", profile1);
DispatchTranscriptionToProfile("Capybaras' teeth grow continuously.",
profile2);
ExpectIsWidgetVisibleOnProfile(true, profile1);
ExpectIsWidgetVisibleOnProfile(true, profile2);
// OnAudioStreamEnd routes to the right profile.
OnAudioStreamEndOnProfile(profile1);
ExpectIsWidgetVisibleOnProfile(false, profile1);
ExpectIsWidgetVisibleOnProfile(true, profile2);
OnAudioStreamEndOnProfile(profile2);
ExpectIsWidgetVisibleOnProfile(false, profile1);
ExpectIsWidgetVisibleOnProfile(false, profile2);
}
#endif // !BUILDFLAG(IS_CHROMEOS)
} // namespace captions