| // 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/ui/views/accessibility/caption_bubble_controller_views.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "chrome/browser/accessibility/caption_controller.h" |
| #include "chrome/browser/accessibility/caption_controller_factory.h" |
| #include "chrome/browser/accessibility/caption_host_impl.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "components/live_caption/views/caption_bubble.h" |
| #include "components/live_caption/views/caption_bubble_model.h" |
| #include "content/public/browser/web_contents.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace captions { |
| |
| // Static |
| std::unique_ptr<CaptionBubbleController> CaptionBubbleController::Create() { |
| return std::make_unique<CaptionBubbleControllerViews>(); |
| } |
| |
| CaptionBubbleControllerViews::CaptionBubbleControllerViews() { |
| caption_bubble_ = new CaptionBubble( |
| base::BindOnce(&CaptionBubbleControllerViews::OnCaptionBubbleDestroyed, |
| base::Unretained(this)), |
| /* hide_on_inactivity= */ true); |
| caption_widget_ = |
| views::BubbleDialogDelegateView::CreateBubble(caption_bubble_); |
| } |
| |
| CaptionBubbleControllerViews::~CaptionBubbleControllerViews() { |
| if (caption_widget_) |
| caption_widget_->CloseNow(); |
| } |
| |
| void CaptionBubbleControllerViews::OnCaptionBubbleDestroyed() { |
| caption_bubble_ = nullptr; |
| caption_widget_ = nullptr; |
| } |
| |
| bool CaptionBubbleControllerViews::OnTranscription( |
| CaptionHostImpl* caption_host_impl, |
| const chrome::mojom::TranscriptionResultPtr& transcription_result) { |
| if (!caption_bubble_) |
| return false; |
| SetActiveModel(caption_host_impl); |
| if (active_model_->IsClosed()) |
| return false; |
| |
| // If the caption bubble has no activity and it receives a final |
| // transcription, don't set text. The speech service sends a final |
| // transcription after several seconds of no audio. This prevents the bubble |
| // reappearing with a final transcription after it had disappeared due to no |
| // activity. |
| if (!caption_bubble_->HasActivity() && transcription_result->is_final) |
| return true; |
| |
| active_model_->SetPartialText(transcription_result->transcription); |
| if (transcription_result->is_final) |
| active_model_->CommitPartialText(); |
| |
| return true; |
| } |
| |
| void CaptionBubbleControllerViews::OnError(CaptionHostImpl* caption_host_impl) { |
| if (!caption_bubble_) |
| return; |
| SetActiveModel(caption_host_impl); |
| if (active_model_->IsClosed()) |
| return; |
| active_model_->OnError(); |
| } |
| |
| void CaptionBubbleControllerViews::OnAudioStreamEnd( |
| CaptionHostImpl* caption_host_impl) { |
| if (!caption_bubble_) |
| return; |
| |
| CaptionBubbleModel* caption_bubble_model = |
| caption_bubble_models_[caption_host_impl].get(); |
| if (active_model_ == caption_bubble_model) { |
| active_model_ = nullptr; |
| caption_bubble_->SetModel(nullptr); |
| } |
| caption_bubble_models_.erase(caption_host_impl); |
| } |
| |
| void CaptionBubbleControllerViews::UpdateCaptionStyle( |
| base::Optional<ui::CaptionStyle> caption_style) { |
| caption_bubble_->UpdateCaptionStyle(caption_style); |
| } |
| |
| void CaptionBubbleControllerViews::SetActiveModel( |
| CaptionHostImpl* caption_host_impl) { |
| if (!caption_bubble_models_.count(caption_host_impl)) { |
| content::WebContents* web_contents = caption_host_impl->GetWebContents(); |
| views::Widget* context_widget = |
| web_contents ? views::Widget::GetTopLevelWidgetForNativeView( |
| web_contents->GetNativeView()) |
| : nullptr; |
| base::Optional<gfx::Rect> context_bounds = base::nullopt; |
| if (context_widget) |
| context_bounds = context_widget->GetClientAreaBoundsInScreen(); |
| caption_bubble_models_.emplace( |
| caption_host_impl, |
| std::make_unique<CaptionBubbleModel>( |
| context_bounds, |
| base::BindRepeating(&CaptionBubbleControllerViews::ActivateContext, |
| base::Unretained(this), web_contents))); |
| } |
| |
| CaptionBubbleModel* caption_bubble_model = |
| caption_bubble_models_[caption_host_impl].get(); |
| if (active_model_ != caption_bubble_model) { |
| active_model_ = caption_bubble_model; |
| caption_bubble_->SetModel(active_model_); |
| } |
| } |
| |
| void CaptionBubbleControllerViews::ActivateContext( |
| content::WebContents* web_contents) { |
| if (!web_contents) |
| return; |
| // Activate the web contents and the browser window that the web contents is |
| // in. Order matters: web contents needs to be active in order for the widget |
| // getter to work. |
| Browser* browser = chrome::FindBrowserWithWebContents(web_contents); |
| TabStripModel* tab_strip_model = browser->tab_strip_model(); |
| int index = tab_strip_model->GetIndexOfWebContents(web_contents); |
| tab_strip_model->ActivateTabAt(index); |
| views::Widget* context_widget = views::Widget::GetTopLevelWidgetForNativeView( |
| web_contents->GetNativeView()); |
| if (context_widget) |
| context_widget->Activate(); |
| } |
| |
| bool CaptionBubbleControllerViews::IsWidgetVisibleForTesting() { |
| return caption_widget_ && caption_widget_->IsVisible(); |
| } |
| |
| std::string CaptionBubbleControllerViews::GetBubbleLabelTextForTesting() { |
| return caption_bubble_ |
| ? base::UTF16ToUTF8( |
| caption_bubble_->GetLabelForTesting()->GetText()) // IN-TEST |
| : ""; |
| } |
| |
| } // namespace captions |