blob: 6fe05144a8d23128e9a736f2cc328deb3a7b272b [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_util.h"
#include "chrome/browser/accessibility/soda_installer.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 "content/public/browser/web_contents.h"
#include "media/base/media_switches.h"
namespace {
const char* const kCaptionStylePrefsToObserve[] = {
prefs::kAccessibilityCaptionsTextSize,
prefs::kAccessibilityCaptionsTextFont,
prefs::kAccessibilityCaptionsTextColor,
prefs::kAccessibilityCaptionsTextOpacity,
prefs::kAccessibilityCaptionsBackgroundColor,
prefs::kAccessibilityCaptionsTextShadow,
prefs::kAccessibilityCaptionsBackgroundOpacity};
constexpr int kSodaCleanUpDelayInDays = 30;
} // namespace
namespace captions {
CaptionController::CaptionController(Profile* profile) : profile_(profile) {}
CaptionController::~CaptionController() = default;
// 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");
}
void CaptionController::Init() {
// Hidden behind a feature flag.
if (!base::FeatureList::IsEnabled(media::kLiveCaption))
return;
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_)
UpdateUIEnabled();
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_) {
// Register SODA component and download speech model.
g_browser_process->local_state()->SetTime(prefs::kSodaScheduledDeletionTime,
base::Time());
speech::SODAInstaller::GetInstance()->InstallSODA(profile_->GetPrefs());
speech::SODAInstaller::GetInstance()->InstallLanguage(profile_->GetPrefs());
} else {
// Schedule SODA to be deleted in 30 days if the feature is not enabled
// before then.
g_browser_process->local_state()->SetTime(
prefs::kSodaScheduledDeletionTime,
base::Time::Now() + base::TimeDelta::FromDays(kSodaCleanUpDelayInDays));
}
UpdateUIEnabled();
}
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::UpdateUIEnabled() {
if (enabled_) {
// 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 caption style prefs.
for (const char* const pref_name : kCaptionStylePrefsToObserve) {
pref_change_registrar_->Add(
pref_name, base::BindRepeating(&CaptionController::UpdateCaptionStyle,
base::Unretained(this)));
}
UpdateCaptionStyle();
} else {
// Destroy caption bubble controllers.
caption_bubble_controllers_.clear();
// Remove observers.
BrowserList::GetInstance()->RemoveObserver(this);
// Remove prefs to observe.
for (const char* const pref_name : kCaptionStylePrefsToObserve) {
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(
content::WebContents* web_contents,
const chrome::mojom::TranscriptionResultPtr& transcription_result) {
Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
if (!browser || !caption_bubble_controllers_.count(browser))
return false;
return caption_bubble_controllers_[browser]->OnTranscription(
transcription_result, web_contents);
}
void CaptionController::OnError(content::WebContents* web_contents) {
Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
if (!browser || !caption_bubble_controllers_.count(browser))
return;
return caption_bubble_controllers_[browser]->OnError(web_contents);
}
CaptionBubbleController*
CaptionController::GetCaptionBubbleControllerForBrowser(Browser* browser) {
if (!browser || !caption_bubble_controllers_.count(browser))
return nullptr;
return caption_bubble_controllers_[browser].get();
}
void CaptionController::UpdateCaptionStyle() {
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