Commit 01dd39f8 authored by Abigail Klein's avatar Abigail Klein Committed by Commit Bot

[Live Caption] If an interval of time passes with no activity, hide the

caption bubble.

If 5 seconds pass with no activity, hide the caption bubble. Activity
is defined as one of the following occurring:
- Transcription received from the speech service
- User interaction, such as clicking a button or focusing the bubble

More refinements to this will occur in a follow-up, such as clearing the
caption text.

Bug: 1055150
Change-Id: I33c96c09bc89bbfe68b1c8899f3aa5289ed44f76
AX-Relnotes: N/A (feature has not launched)
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2518129Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Commit-Queue: Abigail Klein <abigailbklein@google.com>
Cr-Commit-Position: refs/heads/master@{#824586}
parent 2fac7fe4
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
#include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_macros.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "base/time/default_tick_clock.h"
#include "base/timer/timer.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "chrome/app/vector_icons/vector_icons.h" #include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/accessibility/caption_controller.h" #include "chrome/browser/accessibility/caption_controller.h"
...@@ -71,6 +73,7 @@ static constexpr int kErrorImageSizeDip = 20; ...@@ -71,6 +73,7 @@ static constexpr int kErrorImageSizeDip = 20;
static constexpr int kErrorMessageBetweenChildSpacingDip = 16; static constexpr int kErrorMessageBetweenChildSpacingDip = 16;
static constexpr int kFocusRingInnerInsetDip = 3; static constexpr int kFocusRingInnerInsetDip = 3;
static constexpr int kWidgetDisplacementWithArrowKeyDip = 16; static constexpr int kWidgetDisplacementWithArrowKeyDip = 16;
static constexpr int kNoActivityIntervalSeconds = 5;
} // namespace } // namespace
...@@ -179,7 +182,8 @@ CaptionBubble::CaptionBubble(views::View* anchor, ...@@ -179,7 +182,8 @@ CaptionBubble::CaptionBubble(views::View* anchor,
destroyed_callback_(std::move(destroyed_callback)), destroyed_callback_(std::move(destroyed_callback)),
ratio_in_parent_x_(kDefaultRatioInParentX), ratio_in_parent_x_(kDefaultRatioInParentX),
ratio_in_parent_y_(kDefaultRatioInParentY), ratio_in_parent_y_(kDefaultRatioInParentY),
browser_view_(browser_view) { browser_view_(browser_view),
tick_clock_(base::DefaultTickClock::GetInstance()) {
// Bubbles that use transparent colors should not paint their ClientViews to a // Bubbles that use transparent colors should not paint their ClientViews to a
// layer as doing so could result in visual artifacts. // layer as doing so could result in visual artifacts.
SetPaintClientToLayer(false); SetPaintClientToLayer(false);
...@@ -194,6 +198,12 @@ CaptionBubble::CaptionBubble(views::View* anchor, ...@@ -194,6 +198,12 @@ CaptionBubble::CaptionBubble(views::View* anchor,
// View::FocusBehavior::ACCESSIBLE_ONLY. However, that does not seem to get // View::FocusBehavior::ACCESSIBLE_ONLY. However, that does not seem to get
// OnFocus() and OnBlur() called so we never draw the custom focus ring. // OnFocus() and OnBlur() called so we never draw the custom focus ring.
SetFocusBehavior(View::FocusBehavior::ALWAYS); SetFocusBehavior(View::FocusBehavior::ALWAYS);
inactivity_timer_ = std::make_unique<base::RetainingOneShotTimer>(
FROM_HERE, base::TimeDelta::FromSeconds(kNoActivityIntervalSeconds),
base::BindRepeating(&CaptionBubble::OnInactivityTimeout,
base::Unretained(this)),
tick_clock_);
inactivity_timer_->Stop();
} }
CaptionBubble::~CaptionBubble() { CaptionBubble::~CaptionBubble() {
...@@ -281,6 +291,11 @@ void CaptionBubble::OnWidgetBoundsChanged(views::Widget* widget, ...@@ -281,6 +291,11 @@ void CaptionBubble::OnWidgetBoundsChanged(views::Widget* widget,
if (out_of_bounds) if (out_of_bounds)
SizeToContents(); SizeToContents();
// If the widget is visible and unfocused, probably due to a mouse drag, reset
// the inactivity timer.
if (GetWidget()->IsVisible() && !HasFocus())
inactivity_timer_->Reset();
} }
void CaptionBubble::Init() { void CaptionBubble::Init() {
...@@ -464,10 +479,12 @@ bool CaptionBubble::AcceleratorPressed(const ui::Accelerator& accelerator) { ...@@ -464,10 +479,12 @@ bool CaptionBubble::AcceleratorPressed(const ui::Accelerator& accelerator) {
void CaptionBubble::OnFocus() { void CaptionBubble::OnFocus() {
frame_->UpdateFocusRing(true); frame_->UpdateFocusRing(true);
inactivity_timer_->Stop();
} }
void CaptionBubble::OnBlur() { void CaptionBubble::OnBlur() {
frame_->UpdateFocusRing(false); frame_->UpdateFocusRing(false);
inactivity_timer_->Reset();
} }
void CaptionBubble::GetAccessibleNodeData(ui::AXNodeData* node_data) { void CaptionBubble::GetAccessibleNodeData(ui::AXNodeData* node_data) {
...@@ -510,6 +527,7 @@ void CaptionBubble::ExpandOrCollapseButtonPressed() { ...@@ -510,6 +527,7 @@ void CaptionBubble::ExpandOrCollapseButtonPressed() {
// TODO(crbug.com/1055150): Ensure that the button keeps focus on mac. // TODO(crbug.com/1055150): Ensure that the button keeps focus on mac.
if (button_had_focus) if (button_had_focus)
new_button->RequestFocus(); new_button->RequestFocus();
inactivity_timer_->Reset();
} }
void CaptionBubble::SetModel(CaptionBubbleModel* model) { void CaptionBubble::SetModel(CaptionBubbleModel* model) {
...@@ -525,6 +543,8 @@ void CaptionBubble::OnTextChanged() { ...@@ -525,6 +543,8 @@ void CaptionBubble::OnTextChanged() {
std::string text = model_->GetFullText(); std::string text = model_->GetFullText();
label_->SetText(base::UTF8ToUTF16(text)); label_->SetText(base::UTF8ToUTF16(text));
UpdateBubbleAndTitleVisibility(); UpdateBubbleAndTitleVisibility();
if (GetWidget()->IsVisible())
inactivity_timer_->Reset();
// Only update ViewAccessibility if accessibility is enabled. // Only update ViewAccessibility if accessibility is enabled.
if (content::BrowserAccessibilityState::GetInstance() if (content::BrowserAccessibilityState::GetInstance()
...@@ -735,6 +755,11 @@ void CaptionBubble::Redraw() { ...@@ -735,6 +755,11 @@ void CaptionBubble::Redraw() {
SizeToContents(); SizeToContents();
} }
void CaptionBubble::OnInactivityTimeout() {
if (GetWidget()->IsVisible())
GetWidget()->Hide();
}
const char* CaptionBubble::GetClassName() const { const char* CaptionBubble::GetClassName() const {
return "CaptionBubble"; return "CaptionBubble";
} }
...@@ -753,4 +778,8 @@ std::vector<std::string> CaptionBubble::GetVirtualChildrenTextForTesting() { ...@@ -753,4 +778,8 @@ std::vector<std::string> CaptionBubble::GetVirtualChildrenTextForTesting() {
return texts; return texts;
} }
base::RetainingOneShotTimer* CaptionBubble::GetInactivityTimerForTesting() {
return inactivity_timer_.get();
}
} // namespace captions } // namespace captions
...@@ -14,6 +14,10 @@ ...@@ -14,6 +14,10 @@
#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/button/button.h" #include "ui/views/controls/button/button.h"
namespace base {
class RetainingOneShotTimer;
}
namespace gfx { namespace gfx {
struct VectorIcon; struct VectorIcon;
} }
...@@ -72,6 +76,10 @@ class CaptionBubble : public views::BubbleDialogDelegateView { ...@@ -72,6 +76,10 @@ class CaptionBubble : public views::BubbleDialogDelegateView {
const char* GetClassName() const override; const char* GetClassName() const override;
std::string GetLabelTextForTesting(); std::string GetLabelTextForTesting();
base::RetainingOneShotTimer* GetInactivityTimerForTesting();
void set_tick_clock_for_testing(const base::TickClock* tick_clock) {
tick_clock_ = tick_clock;
}
protected: protected:
// views::BubbleDialogDelegateView: // views::BubbleDialogDelegateView:
...@@ -129,6 +137,11 @@ class CaptionBubble : public views::BubbleDialogDelegateView { ...@@ -129,6 +137,11 @@ class CaptionBubble : public views::BubbleDialogDelegateView {
const gfx::Range& range); const gfx::Range& range);
std::vector<std::string> GetVirtualChildrenTextForTesting(); std::vector<std::string> GetVirtualChildrenTextForTesting();
// After 5 seconds of inactivity, hide the caption bubble. Activity is defined
// as transcription received from the speech service or user interacting with
// the bubble through focus, pressing buttons, or dragging.
void OnInactivityTimeout();
// Unowned. Owned by views hierarchy. // Unowned. Owned by views hierarchy.
views::Label* label_; views::Label* label_;
views::Label* title_; views::Label* title_;
...@@ -162,6 +175,11 @@ class CaptionBubble : public views::BubbleDialogDelegateView { ...@@ -162,6 +175,11 @@ class CaptionBubble : public views::BubbleDialogDelegateView {
// Whether the caption bubble is expanded to show more lines of text. // Whether the caption bubble is expanded to show more lines of text.
bool is_expanded_ = false; bool is_expanded_ = false;
// A timer which causes the bubble to hide if there is no activity after a
// specified interval.
std::unique_ptr<base::RetainingOneShotTimer> inactivity_timer_;
const base::TickClock* tick_clock_;
}; };
} // namespace captions } // namespace captions
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
#include <memory> #include <memory>
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_mock_time_message_loop_task_runner.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_commands.h"
...@@ -161,6 +161,15 @@ class CaptionBubbleControllerViewsTest : public InProcessBrowserTest { ...@@ -161,6 +161,15 @@ class CaptionBubbleControllerViewsTest : public InProcessBrowserTest {
return GetBubble()->GetVirtualChildrenTextForTesting(); return GetBubble()->GetVirtualChildrenTextForTesting();
} }
void SetTickClockForTesting(const base::TickClock* tick_clock) {
GetController()->caption_bubble_->set_tick_clock_for_testing(tick_clock);
}
void UnfocusCaptionWidget() {
GetController()->caption_bubble_->AcceleratorPressed(
ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
}
private: private:
std::unique_ptr<CaptionBubbleControllerViews> controller_; std::unique_ptr<CaptionBubbleControllerViews> controller_;
}; };
...@@ -1034,4 +1043,47 @@ IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest, ...@@ -1034,4 +1043,47 @@ IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest,
} }
#endif #endif
IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest, HidesAfterInactivity) {
// Use a ScopedMockTimeMessageLoopTaskRunner to test the inactivity timer with
// a mock tick clock that replaces the default tick clock with mock time.
base::ScopedMockTimeMessageLoopTaskRunner test_task_runner;
SetTickClockForTesting(test_task_runner->GetMockTickClock());
// Caption bubble hides after 5 seconds without receiving a transcription.
OnPartialTranscription("Bowhead whales can live for over 200 years.");
EXPECT_TRUE(IsWidgetVisible());
ASSERT_TRUE(GetBubble()->GetInactivityTimerForTesting()->IsRunning());
test_task_runner->FastForwardBy(base::TimeDelta::FromSeconds(5));
EXPECT_FALSE(IsWidgetVisible());
// Caption bubble becomes visible when transcription is received, and stays
// visible if transcriptions are received before 5 seconds have passed.
OnPartialTranscription("Killer whales");
EXPECT_TRUE(IsWidgetVisible());
test_task_runner->FastForwardBy(base::TimeDelta::FromSeconds(4));
EXPECT_TRUE(IsWidgetVisible());
OnPartialTranscription("Killer whales travel in matrifocal groups");
EXPECT_TRUE(IsWidgetVisible());
test_task_runner->FastForwardBy(base::TimeDelta::FromSeconds(4));
EXPECT_TRUE(IsWidgetVisible());
OnFinalTranscription(
"Killer whales travel in matrifocal groups--a family unit centered on "
"the mother.");
EXPECT_TRUE(IsWidgetVisible());
test_task_runner->FastForwardBy(base::TimeDelta::FromSeconds(4));
EXPECT_TRUE(IsWidgetVisible());
// Caption bubble stays visible while it has focus.
GetBubble()->RequestFocus();
EXPECT_TRUE(IsWidgetVisible());
test_task_runner->FastForwardBy(base::TimeDelta::FromSeconds(10));
EXPECT_TRUE(IsWidgetVisible());
UnfocusCaptionWidget();
EXPECT_FALSE(GetBubble()->HasFocus());
EXPECT_TRUE(IsWidgetVisible());
test_task_runner->FastForwardBy(base::TimeDelta::FromSeconds(5));
EXPECT_FALSE(IsWidgetVisible());
}
} // namespace captions } // namespace captions
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment