blob: da4d8450c69ceef12ae482950e4a2a19c45cd970 [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/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/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/caption_bubble_controller.h"
#include "chrome/common/pref_names.h"
#include "components/live_caption/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",
media::IsLiveCaptionFeatureEnabled());
// Hidden behind a feature flag.
if (!media::IsLiveCaptionFeatureEnabled())
return;
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(), g_browser_process->local_state());
}
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()->Init(
profile_->GetPrefs(), g_browser_process->local_state());
}
}
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;
caption_bubble_controller_ = CaptionBubbleController::Create();
caption_bubble_controller_->UpdateCaptionStyle(caption_style_);
// 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;
caption_bubble_controller_.reset(nullptr);
// Remove native theme observer.
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_);
}
bool CaptionController::DispatchTranscription(
CaptionHostImpl* caption_host_impl,
const chrome::mojom::TranscriptionResultPtr& transcription_result) {
if (!caption_bubble_controller_)
return false;
return caption_bubble_controller_->OnTranscription(caption_host_impl,
transcription_result);
}
void CaptionController::OnError(CaptionHostImpl* caption_host_impl) {
if (!caption_bubble_controller_)
return;
caption_bubble_controller_->OnError(caption_host_impl);
}
void CaptionController::OnAudioStreamEnd(CaptionHostImpl* caption_host_impl) {
if (!caption_bubble_controller_)
return;
caption_bubble_controller_->OnAudioStreamEnd(caption_host_impl);
}
void CaptionController::OnLanguageIdentificationEvent(
const media::mojom::LanguageIdentificationEventPtr& event) {
// TODO(crbug.com/1175357): Implement the UI for language identification.
}
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 */);
caption_bubble_controller_->UpdateCaptionStyle(caption_style_);
}
} // namespace captions