blob: 69d115a4e34c04163c76bbbacf741db5c35a2dcc [file] [log] [blame]
// Copyright (c) 2020 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/accessibility/caption_controller.h"
#include <memory>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/accessibility/caption_host_impl.h"
#include "chrome/browser/accessibility/caption_util.h"
#include "chrome/browser/accessibility/soda_installer.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/caption_bubble_controller.h"
#include "chrome/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/soda/constants.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "media/base/media_switches.h"
#include "ui/native_theme/native_theme.h"
namespace {
const char* const kCaptionStylePrefsToObserve[] = {
prefs::kAccessibilityCaptionsTextSize,
prefs::kAccessibilityCaptionsTextFont,
prefs::kAccessibilityCaptionsTextColor,
prefs::kAccessibilityCaptionsTextOpacity,
prefs::kAccessibilityCaptionsBackgroundColor,
prefs::kAccessibilityCaptionsTextShadow,
prefs::kAccessibilityCaptionsBackgroundOpacity};
} // namespace
namespace captions {
CaptionController::CaptionController(Profile* profile) : profile_(profile) {}
CaptionController::~CaptionController() {
if (enabled_) {
enabled_ = false;
StopLiveCaption();
}
}
// static
void CaptionController::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterBooleanPref(
prefs::kLiveCaptionEnabled, false,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
// Initially default the language to en-US.
registry->RegisterStringPref(prefs::kLiveCaptionLanguageCode, "en-US",
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
}
void CaptionController::Init() {
base::UmaHistogramBoolean("Accessibility.LiveCaption.FeatureEnabled",
base::FeatureList::IsEnabled(media::kLiveCaption));
// Hidden behind a feature flag.
if (!base::FeatureList::IsEnabled(media::kLiveCaption))
return;
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Return early if current profile is a signin profile (as opposed to a user
// profile).
if (ash::ProfileHelper::IsSigninProfile(profile_))
return;
#endif
base::UmaHistogramBoolean(
"Accessibility.LiveCaption.UseSodaForLiveCaption",
base::FeatureList::IsEnabled(media::kUseSodaForLiveCaption));
pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
pref_change_registrar_->Init(profile_->GetPrefs());
auto* command_line = base::CommandLine::ForCurrentProcess();
if (command_line &&
command_line->HasSwitch(switches::kEnableLiveCaptionPrefForTesting)) {
profile_->GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled, true);
}
pref_change_registrar_->Add(
prefs::kLiveCaptionEnabled,
base::BindRepeating(&CaptionController::OnLiveCaptionEnabledChanged,
base::Unretained(this)));
pref_change_registrar_->Add(
prefs::kLiveCaptionLanguageCode,
base::BindRepeating(&CaptionController::OnLiveCaptionLanguageChanged,
base::Unretained(this)));
enabled_ = IsLiveCaptionEnabled();
if (enabled_) {
StartLiveCaption();
} else {
StopLiveCaption();
}
content::BrowserAccessibilityState::GetInstance()
->AddUIThreadHistogramCallback(base::BindOnce(
&CaptionController::UpdateAccessibilityCaptionHistograms,
base::Unretained(this)));
}
void CaptionController::OnLiveCaptionEnabledChanged() {
bool enabled = IsLiveCaptionEnabled();
if (enabled == enabled_)
return;
enabled_ = enabled;
if (enabled) {
StartLiveCaption();
} else {
StopLiveCaption();
speech::SodaInstaller::GetInstance()->SetUninstallTimer(
profile_->GetPrefs(), g_browser_process->local_state());
}
}
void CaptionController::OnLiveCaptionLanguageChanged() {
if (enabled_)
speech::SodaInstaller::GetInstance()->InstallLanguage(profile_->GetPrefs());
}
bool CaptionController::IsLiveCaptionEnabled() {
PrefService* profile_prefs = profile_->GetPrefs();
return profile_prefs->GetBoolean(prefs::kLiveCaptionEnabled);
}
void CaptionController::StartLiveCaption() {
DCHECK(enabled_);
if (!base::FeatureList::IsEnabled(media::kUseSodaForLiveCaption)) {
CreateUI();
return;
}
// The SodaInstaller determines whether SODA is already on the device and
// whether or not to download. Once SODA is on the device and ready, the
// SODAInstaller calls OnSodaInstalled on its observers. The UI is created at
// that time.
if (speech::SodaInstaller::GetInstance()->IsSodaInstalled()) {
CreateUI();
} else {
speech::SodaInstaller::GetInstance()->AddObserver(this);
speech::SodaInstaller::GetInstance()->InitForProfileIfAppropriate(profile_);
}
}
void CaptionController::StopLiveCaption() {
DCHECK(!enabled_);
speech::SodaInstaller::GetInstance()->RemoveObserver(this);
DestroyUI();
}
void CaptionController::OnSodaInstalled() {
// Live Caption should always be enabled when this is called. If Live Caption
// has been disabled, then this should not be observing the SodaInstaller
// anymore.
DCHECK(enabled_);
speech::SodaInstaller::GetInstance()->RemoveObserver(this);
CreateUI();
}
void CaptionController::CreateUI() {
DCHECK(enabled_);
if (is_ui_constructed_)
return;
DCHECK(!base::FeatureList::IsEnabled(media::kUseSodaForLiveCaption) ||
speech::SodaInstaller::GetInstance()->IsSodaInstalled());
is_ui_constructed_ = true;
// Create captions UI in each browser view.
for (Browser* browser : *BrowserList::GetInstance()) {
OnBrowserAdded(browser);
}
// Add observers to the BrowserList for new browser views being added.
BrowserList::GetInstance()->AddObserver(this);
// Observe native theme changes for caption style updates.
ui::NativeTheme::GetInstanceForWeb()->AddObserver(this);
// Observe caption style prefs.
for (const char* const pref_name : kCaptionStylePrefsToObserve) {
DCHECK(!pref_change_registrar_->IsObserved(pref_name));
pref_change_registrar_->Add(
pref_name,
base::BindRepeating(&CaptionController::OnCaptionStyleUpdated,
base::Unretained(this)));
}
OnCaptionStyleUpdated();
}
void CaptionController::DestroyUI() {
DCHECK(!enabled_);
if (!is_ui_constructed_)
return;
is_ui_constructed_ = false;
// Destroy caption bubble controllers.
caption_bubble_controllers_.clear();
// Remove observers.
BrowserList::GetInstance()->RemoveObserver(this);
ui::NativeTheme::GetInstanceForWeb()->RemoveObserver(this);
// Remove prefs to observe.
for (const char* const pref_name : kCaptionStylePrefsToObserve) {
DCHECK(pref_change_registrar_->IsObserved(pref_name));
pref_change_registrar_->Remove(pref_name);
}
}
void CaptionController::UpdateAccessibilityCaptionHistograms() {
base::UmaHistogramBoolean("Accessibility.LiveCaption", enabled_);
}
void CaptionController::OnBrowserAdded(Browser* browser) {
if (browser->profile() != profile_ &&
browser->profile()->GetOriginalProfile() != profile_) {
return;
}
DCHECK(!caption_bubble_controllers_.count(browser));
caption_bubble_controllers_[browser] =
CaptionBubbleController::Create(browser);
caption_bubble_controllers_[browser]->UpdateCaptionStyle(caption_style_);
}
void CaptionController::OnBrowserRemoved(Browser* browser) {
if (browser->profile() != profile_ &&
browser->profile()->GetOriginalProfile() != profile_) {
return;
}
DCHECK(caption_bubble_controllers_.count(browser));
caption_bubble_controllers_.erase(browser);
}
bool CaptionController::DispatchTranscription(
CaptionHostImpl* caption_host_impl,
const chrome::mojom::TranscriptionResultPtr& transcription_result) {
Browser* browser =
chrome::FindBrowserWithWebContents(caption_host_impl->GetWebContents());
if (!browser || !caption_bubble_controllers_.count(browser))
return false;
return caption_bubble_controllers_[browser]->OnTranscription(
caption_host_impl, transcription_result);
}
void CaptionController::OnError(CaptionHostImpl* caption_host_impl) {
Browser* browser =
chrome::FindBrowserWithWebContents(caption_host_impl->GetWebContents());
if (!browser || !caption_bubble_controllers_.count(browser))
return;
caption_bubble_controllers_[browser]->OnError(caption_host_impl);
}
void CaptionController::OnAudioStreamEnd(CaptionHostImpl* caption_host_impl) {
Browser* browser =
chrome::FindBrowserWithWebContents(caption_host_impl->GetWebContents());
if (!browser || !caption_bubble_controllers_.count(browser))
return;
caption_bubble_controllers_[browser]->OnAudioStreamEnd(caption_host_impl);
}
void CaptionController::OnLanguageIdentificationEvent(
const media::mojom::LanguageIdentificationEventPtr& event) {
// TODO(crbug.com/1175357): Implement the UI for language identification.
}
CaptionBubbleController*
CaptionController::GetCaptionBubbleControllerForBrowser(Browser* browser) {
if (!browser || !caption_bubble_controllers_.count(browser))
return nullptr;
return caption_bubble_controllers_[browser].get();
}
void CaptionController::OnCaptionStyleUpdated() {
PrefService* profile_prefs = profile_->GetPrefs();
// Metrics are recorded when passing the caption prefs to the browser, so do
// not duplicate them here.
caption_style_ = GetCaptionStyleFromUserSettings(profile_prefs,
false /* record_metrics */);
for (const auto& item : caption_bubble_controllers_) {
caption_bubble_controllers_[item.first]->UpdateCaptionStyle(caption_style_);
}
}
} // namespace captions