Skip to content

Commit

Permalink
gd: Add a recording timer to the GameDashboardContext
Browse files Browse the repository at this point in the history
Adding a timer that starts when the game window starts recording, and
stops when the recordding ends. During the recording session, the
context notifies the main menu of the recording duration.

Bug: b:273641154
Test: Ran ash unit tests
Change-Id: I9cfe2f28f3d21e0428d29f9e3d82f9c8d392c702
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4779484
Auto-Submit: Prameet Shah <phshah@chromium.org>
Reviewed-by: Ahmed Fakhry <afakhry@chromium.org>
Commit-Queue: Prameet Shah <phshah@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1188131}
  • Loading branch information
Prameet Shah authored and Chromium LUCI CQ committed Aug 25, 2023
1 parent 8f00676 commit 8d3f6cc
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 2 deletions.
45 changes: 44 additions & 1 deletion ash/game_dashboard/game_dashboard_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
#include "ash/game_dashboard/game_dashboard_context.h"

#include <memory>
#include <string>

#include "ash/game_dashboard/game_dashboard_main_menu_view.h"
#include "ash/game_dashboard/game_dashboard_toolbar_view.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/pill_button.h"
#include "base/i18n/time_formatting.h"
#include "base/timer/timer.h"
#include "chromeos/ui/frame/frame_header.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
Expand All @@ -25,6 +28,8 @@ namespace ash {

namespace {

constexpr base::TimeDelta kCountUpTimerRefreshInterval = base::Seconds(1);

// Number of pixels to add to the top and bottom of the main menu button so
// that it's centered within the frame header.
static const int kMainMenuButtonVerticalPaddingDp = 3;
Expand Down Expand Up @@ -149,7 +154,17 @@ bool GameDashboardContext::IsToolbarVisible() const {
}

void GameDashboardContext::OnRecordingStarted(bool is_recording_game_window) {
// TODO(b/273641154): Update the the main menu button to the recording state.
if (is_recording_game_window) {
// TODO(b/273641154): Update the the main menu button to the recording
// state.
CHECK(!recording_timer_.IsRunning());
DCHECK(recording_start_time_.is_null());
DCHECK(recording_duration_.empty());
recording_start_time_ = base::Time::Now();
OnUpdateRecordingTimer();
recording_timer_.Start(FROM_HERE, kCountUpTimerRefreshInterval, this,
&GameDashboardContext::OnUpdateRecordingTimer);
}
if (main_menu_view_) {
main_menu_view_->OnRecordingStarted(is_recording_game_window);
}
Expand All @@ -159,6 +174,10 @@ void GameDashboardContext::OnRecordingStarted(bool is_recording_game_window) {
}

void GameDashboardContext::OnRecordingEnded() {
// Resetting the timer will stop the timer.
recording_timer_.Stop();
recording_start_time_ = base::Time();
recording_duration_.clear();
// TODO(b/273641154): Update the the main menu button to the default state.
if (main_menu_view_) {
main_menu_view_->OnRecordingEnded();
Expand Down Expand Up @@ -268,4 +287,28 @@ void GameDashboardContext::AnimateToolbarWidgetBoundsChange(
.SetTransform(layer, gfx::Transform(), gfx::Tween::ACCEL_0_80_DECEL_80);
}

void GameDashboardContext::OnUpdateRecordingTimer() {
DCHECK(!recording_start_time_.is_null());
const base::TimeDelta delta = base::Time::Now() - recording_start_time_;
std::u16string duration;
if (!base::TimeDurationFormatWithSeconds(
delta, base::DurationFormatWidth::DURATION_WIDTH_NUMERIC,
&duration)) {
VLOG(1) << "Error converting the duration to a string: " << duration;
return;
}
// Remove the leading `0:` for durations less than an hour.
if (delta < base::Hours(1)) {
base::ReplaceFirstSubstringAfterOffset(&duration, 0, u"0:", u"");
}

recording_duration_ = duration;

// TODO(b/273641154): Update the the main menu button with the recording
// duration.
if (main_menu_view_) {
main_menu_view_->UpdateRecordingDuration(duration);
}
}

} // namespace ash
20 changes: 19 additions & 1 deletion ash/game_dashboard/game_dashboard_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
#ifndef ASH_GAME_DASHBOARD_GAME_DASHBOARD_CONTEXT_H_
#define ASH_GAME_DASHBOARD_GAME_DASHBOARD_CONTEXT_H_

#include <memory>

#include "ash/game_dashboard/game_dashboard_widget.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"

Expand All @@ -22,7 +25,7 @@ class GameDashboardToolbarView;

// This class manages Game Dashboard related UI for a given `aura::Window`, and
// its instance is managed by the `GameDashboardController`.
class GameDashboardContext {
class ASH_EXPORT GameDashboardContext {
public:
// Indicator for the 4 quadrants that the toolbar is able to be placed.
enum class ToolbarSnapLocation {
Expand Down Expand Up @@ -63,6 +66,8 @@ class GameDashboardContext {
// Closes the main menu. Clears `main_menu_widget_` and `main_menu_view_`.
void CloseMainMenu();

bool IsMainMenuOpen() const { return main_menu_view_; }

// Toggles the creation/deletion of the toolbar within the game window.
// Returns the toolbar visibility state.
bool ToggleToolbar();
Expand Down Expand Up @@ -108,6 +113,10 @@ class GameDashboardContext {
// it transfers from the previous location.
void AnimateToolbarWidgetBoundsChange(const gfx::Rect& target_screen_bounds);

// Repeating timer callback that notifies `main_menu_view_` of the video
// recording session duration.
void OnUpdateRecordingTimer();

const raw_ptr<aura::Window, ExperimentalAsh> game_window_;

// Main menu button widget for the Game Dashboard.
Expand All @@ -131,6 +140,15 @@ class GameDashboardContext {
// Owned by the views hierarchy.
raw_ptr<GameDashboardToolbarView, ExperimentalAsh> toolbar_view_ = nullptr;

// A repeating timer to keep track of the recording session duration.
base::RepeatingTimer recording_timer_;

// Start time of when `recording_timer_` started.
base::Time recording_start_time_;

// Duration since `recording_timer_` started.
std::u16string recording_duration_;

base::WeakPtrFactory<GameDashboardContext> weak_ptr_factory_{this};
};

Expand Down
13 changes: 13 additions & 0 deletions ash/game_dashboard/game_dashboard_context_test_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include "ash/game_dashboard/game_dashboard_context_test_api.h"

#include <string>

#include "ash/capture_mode/capture_mode_test_util.h"
#include "ash/game_dashboard/game_dashboard_context.h"
#include "ash/game_dashboard/game_dashboard_main_menu_view.h"
Expand All @@ -12,6 +14,7 @@
#include "ash/style/icon_button.h"
#include "ash/style/pill_button.h"
#include "ash/system/unified/feature_tile.h"
#include "base/timer/timer.h"
#include "base/types/cxx23_to_underlying.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/controls/button/button.h"
Expand All @@ -27,6 +30,16 @@ GameDashboardContextTestApi::GameDashboardContextTestApi(
CHECK(event_generator_);
}

const base::RepeatingTimer& GameDashboardContextTestApi::GetRecordingTimer()
const {
return context_->recording_timer_;
}

const std::u16string& GameDashboardContextTestApi::GetRecordingDuration()
const {
return context_->recording_duration_;
}

GameDashboardWidget* GameDashboardContextTestApi::GetMainMenuButtonWidget() {
return context_->main_menu_button_widget();
}
Expand Down
8 changes: 8 additions & 0 deletions ash/game_dashboard/game_dashboard_context_test_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
#ifndef ASH_GAME_DASHBOARD_GAME_DASHBOARD_CONTEXT_TEST_API_H_
#define ASH_GAME_DASHBOARD_GAME_DASHBOARD_CONTEXT_TEST_API_H_

#include <string>

#include "ash/game_dashboard/game_dashboard_context.h"
#include "base/memory/raw_ptr.h"

namespace base {
class RepeatingTimer;
} // namespace base

namespace ui::test {
class EventGenerator;
} // namespace ui::test
Expand Down Expand Up @@ -40,6 +46,8 @@ class GameDashboardContextTestApi {
~GameDashboardContextTestApi() = default;

GameDashboardContext* context() { return context_; }
const base::RepeatingTimer& GetRecordingTimer() const;
const std::u16string& GetRecordingDuration() const;

// Returns the main menu button widget and button.
GameDashboardWidget* GetMainMenuButtonWidget();
Expand Down
60 changes: 60 additions & 0 deletions ash/game_dashboard/game_dashboard_context_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "ash/system/unified/feature_tile.h"
#include "ash/wallpaper/wallpaper_controller_test_api.h"
#include "base/check.h"
#include "base/timer/timer.h"
#include "chromeos/ui/base/window_properties.h"
#include "chromeos/ui/frame/frame_header.h"
#include "chromeos/ui/wm/window_util.h"
Expand Down Expand Up @@ -236,6 +237,14 @@ class GameDashboardContextTest : public GameDashboardTestBase {
EXPECT_TRUE(record_game_button->GetEnabled());
EXPECT_FALSE(record_game_button->toggled());
}
const base::RepeatingTimer& recording_window_timer =
recording_window_test_api->GetRecordingTimer();
const base::RepeatingTimer& other_window_timer =
other_window_test_api->GetRecordingTimer();

// Verify the recording timer is not running in both windows.
EXPECT_FALSE(recording_window_timer.IsRunning());
EXPECT_FALSE(other_window_timer.IsRunning());

// Activate the recording_window.
auto* recording_window =
Expand All @@ -251,6 +260,10 @@ class GameDashboardContextTest : public GameDashboardTestBase {
// closed it.
recording_window_test_api->OpenTheMainMenu();

// Verify the recording timer is only running in `recording_window`.
EXPECT_TRUE(recording_window_timer.IsRunning());
EXPECT_FALSE(other_window_timer.IsRunning());

// Retrieve the record game buttons from both windows.
auto* recording_window_record_game_tile =
recording_window_test_api->GetMainMenuRecordGameTile();
Expand Down Expand Up @@ -297,6 +310,10 @@ class GameDashboardContextTest : public GameDashboardTestBase {
EXPECT_FALSE(other_window_record_game_tile->IsToggled());
EXPECT_FALSE(other_window_record_game_button->toggled());

// Verify the recording timer is not running in both windows.
EXPECT_FALSE(recording_window_timer.IsRunning());
EXPECT_FALSE(other_window_timer.IsRunning());

// Close the toolbar and main menu in both windows.
for (auto* test_api : {recording_window_test_api, other_window_test_api}) {
wm::ActivateWindow(test_api->context()->game_window());
Expand Down Expand Up @@ -530,6 +547,45 @@ TEST_F(GameDashboardContextTest, TwoGameWindowsRecordingState) {
/*other_window_test_api=*/test_api_.get());
}

TEST_F(GameDashboardContextTest, RecordingTimerStringFormat) {
// Create an ARC game window.
CreateGameWindow(/*is_arc_window=*/true);

// Start recording the game window.
test_api_->OpenTheMainMenu();
test_api_->OpenTheToolbar();
auto* record_game_button = test_api_->GetToolbarRecordGameButton();
ASSERT_TRUE(record_game_button);
LeftClickOn(record_game_button);

// Get timer and verify it's running.
const base::RepeatingTimer& timer = test_api_->GetRecordingTimer();
EXPECT_TRUE(timer.IsRunning());

// Verify initial time of 0 seconds.
EXPECT_EQ(u"00:00", test_api_->GetRecordingDuration());

// Advance clock by 1 minute, and verify overflow from seconds to minutes.
AdvanceClock(base::Minutes(1));
EXPECT_EQ(u"01:00", test_api_->GetRecordingDuration());

// Advance clock by 30 seconds.
AdvanceClock(base::Seconds(30));
EXPECT_EQ(u"01:30", test_api_->GetRecordingDuration());

// Advance clock by 50 minutes.
AdvanceClock(base::Minutes(50));
EXPECT_EQ(u"51:30", test_api_->GetRecordingDuration());

// Advance clock by 9 minutes, and verify overflow from minutes to hours.
AdvanceClock(base::Minutes(9));
EXPECT_EQ(u"1:00:30", test_api_->GetRecordingDuration());

// Advance clock by 23 hours, and verify hours doesn't overflow to days.
AdvanceClock(base::Hours(23));
EXPECT_EQ(u"24:00:30", test_api_->GetRecordingDuration());
}

// -----------------------------------------------------------------------------
// GameTypeGameDashboardContextTest:
// Test fixture to test both ARC and GeForceNow game window depending on the
Expand Down Expand Up @@ -1073,9 +1129,11 @@ class GameDashboardStartAndStopCaptureSessionTest
// parameters.
TEST_P(GameDashboardStartAndStopCaptureSessionTest, RecordGameFromMainMenu) {
auto* capture_mode_controller = CaptureModeController::Get();
const base::RepeatingTimer& timer = test_api_->GetRecordingTimer();

test_api_->OpenTheMainMenu();
EXPECT_FALSE(capture_mode_controller->is_recording_in_progress());
EXPECT_FALSE(timer.IsRunning());

if (should_start_from_main_menu_) {
// Retrieve the record game tile from the main menu.
Expand All @@ -1098,6 +1156,7 @@ TEST_P(GameDashboardStartAndStopCaptureSessionTest, RecordGameFromMainMenu) {
}

EXPECT_TRUE(capture_mode_controller->is_recording_in_progress());
EXPECT_TRUE(timer.IsRunning());

if (should_stop_from_main_menu_) {
// Stop the video recording from the main menu.
Expand All @@ -1116,6 +1175,7 @@ TEST_P(GameDashboardStartAndStopCaptureSessionTest, RecordGameFromMainMenu) {
LeftClickOn(test_api_->GetToolbarRecordGameButton());
}
EXPECT_FALSE(capture_mode_controller->is_recording_in_progress());
EXPECT_FALSE(timer.IsRunning());
WaitForCaptureFileToBeSaved();
}

Expand Down
6 changes: 6 additions & 0 deletions ash/game_dashboard/game_dashboard_main_menu_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,12 @@ void GameDashboardMainMenuView::OnRecordingEnded() {
UpdateRecordGameTile(/*is_recording_game_window=*/false);
}

void GameDashboardMainMenuView::UpdateRecordingDuration(
const std::u16string& duration) {
// TODO(b/295070122): Update `record_game_tile_`'s sub-label text to
// `duration`.
}

void GameDashboardMainMenuView::OnToolbarTilePressed() {
toolbar_tile_->SetToggled(context_->ToggleToolbar());
}
Expand Down
4 changes: 4 additions & 0 deletions ash/game_dashboard/game_dashboard_main_menu_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ class ASH_EXPORT GameDashboardMainMenuView
// `CaptureModeController` has ended a recording session or was aborted.
void OnRecordingEnded();

// Updates the `record_game_tile_`'s sub-label with `duration`, showing the
// recording duration.
void UpdateRecordingDuration(const std::u16string& duration);

private:
friend class GameDashboardContextTestApi;

Expand Down
5 changes: 5 additions & 0 deletions ash/game_dashboard/game_dashboard_test_base.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ void GameDashboardTestBase::SetUp() {
EXPECT_TRUE(features::IsGameDashboardEnabled());
}

void GameDashboardTestBase::AdvanceClock(base::TimeDelta delta) {
task_environment()->AdvanceClock(delta);
task_environment()->RunUntilIdle();
}

bool GameDashboardTestBase::IsControllerObservingWindow(
aura::Window* window) const {
return GameDashboardController::Get()->window_observations_.IsObservingSource(
Expand Down
2 changes: 2 additions & 0 deletions ash/game_dashboard/game_dashboard_test_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class GameDashboardTestBase : public AshTestBase {
// AshTestBase:
void SetUp() override;

void AdvanceClock(base::TimeDelta delta);

protected:
// Returns true if the `GameDashboardController` is observing the `window`.
bool IsControllerObservingWindow(aura::Window* window) const;
Expand Down

0 comments on commit 8d3f6cc

Please sign in to comment.