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 @@
#include "base/metrics/histogram_macros.h"
#include "base/strings/string16.h"
#include "base/time/default_tick_clock.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/accessibility/caption_controller.h"
......@@ -71,6 +73,7 @@ static constexpr int kErrorImageSizeDip = 20;
static constexpr int kErrorMessageBetweenChildSpacingDip = 16;
static constexpr int kFocusRingInnerInsetDip = 3;
static constexpr int kWidgetDisplacementWithArrowKeyDip = 16;
static constexpr int kNoActivityIntervalSeconds = 5;
} // namespace
......@@ -179,7 +182,8 @@ CaptionBubble::CaptionBubble(views::View* anchor,
destroyed_callback_(std::move(destroyed_callback)),
ratio_in_parent_x_(kDefaultRatioInParentX),
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
// layer as doing so could result in visual artifacts.
SetPaintClientToLayer(false);
......@@ -194,6 +198,12 @@ CaptionBubble::CaptionBubble(views::View* anchor,
// View::FocusBehavior::ACCESSIBLE_ONLY. However, that does not seem to get
// OnFocus() and OnBlur() called so we never draw the custom focus ring.
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() {
......@@ -281,6 +291,11 @@ void CaptionBubble::OnWidgetBoundsChanged(views::Widget* widget,
if (out_of_bounds)
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() {
......@@ -464,10 +479,12 @@ bool CaptionBubble::AcceleratorPressed(const ui::Accelerator& accelerator) {
void CaptionBubble::OnFocus() {
frame_->UpdateFocusRing(true);
inactivity_timer_->Stop();
}
void CaptionBubble::OnBlur() {
frame_->UpdateFocusRing(false);
inactivity_timer_->Reset();
}
void CaptionBubble::GetAccessibleNodeData(ui::AXNodeData* node_data) {
......@@ -510,6 +527,7 @@ void CaptionBubble::ExpandOrCollapseButtonPressed() {
// TODO(crbug.com/1055150): Ensure that the button keeps focus on mac.
if (button_had_focus)
new_button->RequestFocus();
inactivity_timer_->Reset();
}
void CaptionBubble::SetModel(CaptionBubbleModel* model) {
......@@ -525,6 +543,8 @@ void CaptionBubble::OnTextChanged() {
std::string text = model_->GetFullText();
label_->SetText(base::UTF8ToUTF16(text));
UpdateBubbleAndTitleVisibility();
if (GetWidget()->IsVisible())
inactivity_timer_->Reset();
// Only update ViewAccessibility if accessibility is enabled.
if (content::BrowserAccessibilityState::GetInstance()
......@@ -735,6 +755,11 @@ void CaptionBubble::Redraw() {
SizeToContents();
}
void CaptionBubble::OnInactivityTimeout() {
if (GetWidget()->IsVisible())
GetWidget()->Hide();
}
const char* CaptionBubble::GetClassName() const {
return "CaptionBubble";
}
......@@ -753,4 +778,8 @@ std::vector<std::string> CaptionBubble::GetVirtualChildrenTextForTesting() {
return texts;
}
base::RetainingOneShotTimer* CaptionBubble::GetInactivityTimerForTesting() {
return inactivity_timer_.get();
}
} // namespace captions
......@@ -14,6 +14,10 @@
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/button/button.h"
namespace base {
class RetainingOneShotTimer;
}
namespace gfx {
struct VectorIcon;
}
......@@ -72,6 +76,10 @@ class CaptionBubble : public views::BubbleDialogDelegateView {
const char* GetClassName() const override;
std::string GetLabelTextForTesting();
base::RetainingOneShotTimer* GetInactivityTimerForTesting();
void set_tick_clock_for_testing(const base::TickClock* tick_clock) {
tick_clock_ = tick_clock;
}
protected:
// views::BubbleDialogDelegateView:
......@@ -129,6 +137,11 @@ class CaptionBubble : public views::BubbleDialogDelegateView {
const gfx::Range& range);
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.
views::Label* label_;
views::Label* title_;
......@@ -162,6 +175,11 @@ class CaptionBubble : public views::BubbleDialogDelegateView {
// Whether the caption bubble is expanded to show more lines of text.
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
......
......@@ -6,8 +6,8 @@
#include <memory>
#include "base/run_loop.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 "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
......@@ -161,6 +161,15 @@ class CaptionBubbleControllerViewsTest : public InProcessBrowserTest {
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:
std::unique_ptr<CaptionBubbleControllerViews> controller_;
};
......@@ -1034,4 +1043,47 @@ IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest,
}
#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
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