blob: 3eec982bdeef530927201224e4585ecf09f5a615 [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/views/caption_bubble_controller_views.h"
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include "base/functional/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "components/live_caption/caption_bubble_context.h"
#include "components/live_caption/caption_bubble_settings.h"
#include "components/live_caption/live_caption_controller.h"
#include "components/live_caption/views/caption_bubble.h"
#include "components/live_caption/views/caption_bubble_model.h"
#include "components/live_caption/views/translation_view_wrapper.h"
#include "components/live_caption/views/translation_view_wrapper_base.h"
#include "components/prefs/pref_service.h"
#include "components/soda/soda_installer.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
namespace captions {
// Static
std::unique_ptr<CaptionBubbleController> CaptionBubbleController::Create(
CaptionBubbleSettings* caption_bubble_settings,
const std::string& application_locale,
std::unique_ptr<TranslationViewWrapperBase> translation_view_wrapper) {
return std::make_unique<CaptionBubbleControllerViews>(
caption_bubble_settings, application_locale,
std::move(translation_view_wrapper));
}
CaptionBubbleControllerViews::CaptionBubbleControllerViews(
CaptionBubbleSettings* caption_bubble_settings,
const std::string& application_locale,
std::unique_ptr<TranslationViewWrapperBase> translation_view_wrapper)
: application_locale_(application_locale) {
caption_bubble_ = new CaptionBubble(
caption_bubble_settings, std::move(translation_view_wrapper),
application_locale,
base::BindOnce(&CaptionBubbleControllerViews::OnCaptionBubbleDestroyed,
base::Unretained(this)));
caption_widget_ =
views::BubbleDialogDelegateView::CreateBubble(caption_bubble_);
caption_bubble_->SetCaptionBubbleStyle();
#if !BUILDFLAG(IS_CHROMEOS)
speech::SodaInstaller* soda_installer = speech::SodaInstaller::GetInstance();
if (soda_installer) {
soda_installer->AddObserver(this);
}
#endif // BUILDFLAG(IS_CHROMEOS)
}
CaptionBubbleControllerViews::~CaptionBubbleControllerViews() {
if (caption_widget_)
caption_widget_->CloseNow();
#if !BUILDFLAG(IS_CHROMEOS)
speech::SodaInstaller* soda_installer = speech::SodaInstaller::GetInstance();
// `soda_installer` is not guaranteed to be valid, since it's possible for
// this class to out-live it. This means that this class cannot use
// ScopedObservation and needs to manage removing the observer itself.
if (soda_installer) {
soda_installer->RemoveObserver(this);
}
#endif // BUILDFLAG(IS_CHROMEOS)
}
void CaptionBubbleControllerViews::OnCaptionBubbleDestroyed() {
caption_bubble_ = nullptr;
caption_widget_ = nullptr;
}
bool CaptionBubbleControllerViews::OnTranscription(
content::RenderFrameHost* rfh,
CaptionBubbleContext* caption_bubble_context,
const media::SpeechRecognitionResult& result) {
if (!caption_bubble_)
return false;
SetActiveModel(caption_bubble_context);
if (active_model_->IsClosed())
return false;
active_model_->SetPartialText(result.transcription);
if (result.is_final)
active_model_->CommitPartialText();
return true;
}
void CaptionBubbleControllerViews::OnError(
CaptionBubbleContext* caption_bubble_context,
CaptionBubbleErrorType error_type,
OnErrorClickedCallback error_clicked_callback,
OnDoNotShowAgainClickedCallback error_silenced_callback) {
if (!caption_bubble_)
return;
SetActiveModel(caption_bubble_context);
if (active_model_->IsClosed())
return;
active_model_->OnError(error_type, std::move(error_clicked_callback),
std::move(error_silenced_callback));
}
void CaptionBubbleControllerViews::OnAudioStreamEnd(
content::RenderFrameHost* rfh,
CaptionBubbleContext* caption_bubble_context) {
if (!caption_bubble_)
return;
auto caption_bubble_model_it =
caption_bubble_models_.find(caption_bubble_context);
if (caption_bubble_model_it == caption_bubble_models_.end()) {
return;
}
if (active_model_ != nullptr &&
active_model_->unique_id() ==
caption_bubble_model_it->second->unique_id()) {
active_model_ = nullptr;
caption_bubble_->SetModel(nullptr);
}
caption_bubble_models_.erase(caption_bubble_model_it);
}
void CaptionBubbleControllerViews::UpdateCaptionStyle(
std::optional<ui::CaptionStyle> caption_style) {
caption_bubble_->UpdateCaptionStyle(caption_style);
}
void CaptionBubbleControllerViews::SetActiveModel(
CaptionBubbleContext* caption_bubble_context) {
auto caption_bubble_model_it =
caption_bubble_models_.find(caption_bubble_context);
if (caption_bubble_model_it == caption_bubble_models_.end()) {
auto caption_bubble_model = std::make_unique<CaptionBubbleModel>(
caption_bubble_context,
base::BindRepeating(
&CaptionBubbleControllerViews::OnSessionEnded,
// Unretained is safe because |CaptionBubbleControllerViews|
// owns |caption_bubble_model|.
base::Unretained(this)));
if (closed_sessions_.contains(caption_bubble_context->GetSessionId())) {
caption_bubble_model->Close();
}
caption_bubble_model_it =
caption_bubble_models_
.emplace(caption_bubble_context, std::move(caption_bubble_model))
.first;
}
if (!caption_bubble_session_observers_.count(
caption_bubble_context->GetSessionId())) {
std::unique_ptr<CaptionBubbleSessionObserver> observer =
caption_bubble_context->GetCaptionBubbleSessionObserver();
if (observer) {
observer->SetEndSessionCallback(
base::BindRepeating(&CaptionBubbleControllerViews::OnSessionReset,
base::Unretained(this)));
caption_bubble_session_observers_.emplace(
caption_bubble_context->GetSessionId(), std::move(observer));
}
}
if (active_model_ == nullptr ||
active_model_->unique_id() !=
caption_bubble_model_it->second->unique_id()) {
active_model_ = caption_bubble_model_it->second.get();
caption_bubble_->SetModel(active_model_);
}
}
void CaptionBubbleControllerViews::OnSessionEnded(
const std::string& session_id) {
// Close all other CaptionBubbleModels that share this WebContents identifier.
for (const auto& caption_bubble_model : caption_bubble_models_) {
if (caption_bubble_model.first->GetSessionId() == session_id) {
caption_bubble_model.second->Close();
}
}
closed_sessions_.insert(session_id);
}
void CaptionBubbleControllerViews::OnSessionReset(
const std::string& session_id) {
if (closed_sessions_.contains(session_id)) {
closed_sessions_.erase(session_id);
}
caption_bubble_session_observers_.erase(session_id);
}
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(
caption_bubble_->GetLabelForTesting()->GetText()) // IN-TEST
: "";
}
void CaptionBubbleControllerViews::CloseActiveModelForTesting() {
if (active_model_)
active_model_->Close();
}
views::Widget* CaptionBubbleControllerViews::GetCaptionWidgetForTesting() {
return caption_widget_;
}
CaptionBubble* CaptionBubbleControllerViews::GetCaptionBubbleForTesting() {
return caption_bubble_;
}
void CaptionBubbleControllerViews::OnLanguageIdentificationEvent(
content::RenderFrameHost* rfh,
CaptionBubbleContext* caption_bubble_context,
const media::mojom::LanguageIdentificationEventPtr& event) {
if (!caption_bubble_) {
return;
}
SetActiveModel(caption_bubble_context);
if (active_model_->IsClosed()) {
return;
}
if (event->asr_switch_result ==
media::mojom::AsrSwitchResult::kSwitchSucceeded) {
active_model_->SetLanguage(event->language);
}
}
void CaptionBubbleControllerViews::OnSodaInstalled(
speech::LanguageCode language_code) {
if (active_model_ && language_code != speech::LanguageCode::kNone) {
active_model_->OnLanguagePackInstalled();
}
}
void CaptionBubbleControllerViews::OnSodaInstallError(
speech::LanguageCode language_code,
speech::SodaInstaller::ErrorCode error_code) {
if (active_model_ && language_code != speech::LanguageCode::kNone) {
active_model_->SetDownloadProgressText(l10n_util::GetStringFUTF16(
IDS_LIVE_CAPTION_LANGUAGE_DOWNLOAD_FAILED,
speech::GetLanguageDisplayName(speech::GetLanguageName(language_code),
application_locale_)));
}
}
void CaptionBubbleControllerViews::OnSodaProgress(
speech::LanguageCode language_code,
int progress) {
if (active_model_ && language_code != speech::LanguageCode::kNone) {
active_model_->SetDownloadProgressText(l10n_util::GetStringFUTF16(
IDS_LIVE_CAPTION_DOWNLOAD_PROGRESS,
speech::GetLanguageDisplayName(speech::GetLanguageName(language_code),
application_locale_),
base::UTF8ToUTF16(base::NumberToString(progress))));
}
}
} // namespace captions