Commit 621663f8 authored by Ahmed Fakhry's avatar Ahmed Fakhry Committed by Commit Bot

capture_mode: Initial work for tracking events that end recording

In this CL, we observe the following events:
- A window being recorded gets closed.
- A display being fullscreen-recorded gets disconnected.
- System shutting down.

The above events end video recording.

BUG=1142994
TEST=Added new tests.

Change-Id: I5f03448d1399297e9589d561d9854b3c8cffc0f4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2516239
Commit-Queue: Ahmed Fakhry <afakhry@chromium.org>
Reviewed-by: default avatarJames Cook <jamescook@chromium.org>
Cr-Commit-Position: refs/heads/master@{#823831}
parent da0f2c9f
...@@ -289,6 +289,8 @@ component("ash") { ...@@ -289,6 +289,8 @@ component("ash") {
"capture_mode/stop_recording_button_tray.h", "capture_mode/stop_recording_button_tray.h",
"capture_mode/video_file_handler.cc", "capture_mode/video_file_handler.cc",
"capture_mode/video_file_handler.h", "capture_mode/video_file_handler.h",
"capture_mode/video_recording_watcher.cc",
"capture_mode/video_recording_watcher.h",
"capture_mode/view_with_ink_drop.h", "capture_mode/view_with_ink_drop.h",
"child_accounts/parent_access_controller_impl.cc", "child_accounts/parent_access_controller_impl.cc",
"child_accounts/parent_access_controller_impl.h", "child_accounts/parent_access_controller_impl.h",
......
...@@ -9,14 +9,14 @@ ...@@ -9,14 +9,14 @@
#include <utility> #include <utility>
#include "ash/capture_mode/capture_mode_session.h" #include "ash/capture_mode/capture_mode_session.h"
#include "ash/capture_mode/stop_recording_button_tray.h" #include "ash/capture_mode/capture_mode_util.h"
#include "ash/capture_mode/video_recording_watcher.h"
#include "ash/public/cpp/ash_features.h" #include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/capture_mode_delegate.h" #include "ash/public/cpp/capture_mode_delegate.h"
#include "ash/public/cpp/holding_space/holding_space_client.h" #include "ash/public/cpp/holding_space/holding_space_client.h"
#include "ash/public/cpp/holding_space/holding_space_controller.h" #include "ash/public/cpp/holding_space/holding_space_controller.h"
#include "ash/public/cpp/notification_utils.h" #include "ash/public/cpp/notification_utils.h"
#include "ash/resources/vector_icons/vector_icons.h" #include "ash/resources/vector_icons/vector_icons.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h" #include "ash/strings/grit/ash_strings.h"
#include "ash/system/status_area_widget.h" #include "ash/system/status_area_widget.h"
...@@ -234,18 +234,6 @@ void CopyImageToClipboard(const gfx::Image& image) { ...@@ -234,18 +234,6 @@ void CopyImageToClipboard(const gfx::Image& image) {
clipboard->WriteClipboardData(std::move(clipboard_data)); clipboard->WriteClipboardData(std::move(clipboard_data));
} }
// Shows the stop-recording button in the Shelf's status area widget. Note that
// the button hides itself when clicked.
void ShowStopRecordingButton(aura::Window* root) {
DCHECK(root);
DCHECK(root->IsRootWindow());
auto* stop_recording_button = RootWindowController::ForWindow(root)
->GetStatusAreaWidget()
->stop_recording_button_tray();
stop_recording_button->SetVisiblePreferred(true);
}
} // namespace } // namespace
CaptureModeController::CaptureModeController( CaptureModeController::CaptureModeController(
...@@ -344,6 +332,10 @@ void CaptureModeController::EndVideoRecording() { ...@@ -344,6 +332,10 @@ void CaptureModeController::EndVideoRecording() {
// with all the frames. // with all the frames.
is_recording_in_progress_ = false; is_recording_in_progress_ = false;
Shell::Get()->UpdateCursorCompositingEnabled(); Shell::Get()->UpdateCursorCompositingEnabled();
capture_mode_util::SetStopRecordingButtonVisibility(
video_recording_watcher_->window_being_recorded()->GetRootWindow(),
false);
video_recording_watcher_.reset();
delegate_->StopObservingRestrictedContent(); delegate_->StopObservingRestrictedContent();
DCHECK(video_file_handler_); DCHECK(video_file_handler_);
...@@ -356,6 +348,12 @@ void CaptureModeController::OpenFeedbackDialog() { ...@@ -356,6 +348,12 @@ void CaptureModeController::OpenFeedbackDialog() {
delegate_->OpenFeedbackDialog(); delegate_->OpenFeedbackDialog();
} }
void CaptureModeController::StartVideoRecordingImmediatelyForTesting() {
DCHECK(IsActive());
DCHECK_EQ(type_, CaptureModeType::kVideo);
OnVideoRecordCountDownFinished();
}
bool CaptureModeController::IsCaptureAllowed() const { bool CaptureModeController::IsCaptureAllowed() const {
const base::Optional<CaptureParams> capture_params = GetCaptureParams(); const base::Optional<CaptureParams> capture_params = GetCaptureParams();
if (!capture_params) if (!capture_params)
...@@ -624,6 +622,8 @@ void CaptureModeController::OnVideoRecordCountDownFinished() { ...@@ -624,6 +622,8 @@ void CaptureModeController::OnVideoRecordCountDownFinished() {
// to be able to record it. // to be able to record it.
is_recording_in_progress_ = true; is_recording_in_progress_ = true;
Shell::Get()->UpdateCursorCompositingEnabled(); Shell::Get()->UpdateCursorCompositingEnabled();
video_recording_watcher_ =
std::make_unique<VideoRecordingWatcher>(this, capture_params->window);
// TODO(afakhry): Choose a real buffer capacity when the recording service is // TODO(afakhry): Choose a real buffer capacity when the recording service is
// in. // in.
...@@ -643,7 +643,8 @@ void CaptureModeController::OnVideoRecordCountDownFinished() { ...@@ -643,7 +643,8 @@ void CaptureModeController::OnVideoRecordCountDownFinished() {
base::BindOnce(&CaptureModeController::InterruptVideoRecording, base::BindOnce(&CaptureModeController::InterruptVideoRecording,
weak_ptr_factory_.GetWeakPtr())); weak_ptr_factory_.GetWeakPtr()));
ShowStopRecordingButton(capture_params->window->GetRootWindow()); capture_mode_util::SetStopRecordingButtonVisibility(
capture_params->window->GetRootWindow(), true);
} }
void CaptureModeController::InterruptVideoRecording() { void CaptureModeController::InterruptVideoRecording() {
......
...@@ -30,6 +30,7 @@ class Time; ...@@ -30,6 +30,7 @@ class Time;
namespace ash { namespace ash {
class CaptureModeSession; class CaptureModeSession;
class VideoRecordingWatcher;
// Controls starting and ending a Capture Mode session and its behavior. // Controls starting and ending a Capture Mode session and its behavior.
class ASH_EXPORT CaptureModeController { class ASH_EXPORT CaptureModeController {
...@@ -79,6 +80,10 @@ class ASH_EXPORT CaptureModeController { ...@@ -79,6 +80,10 @@ class ASH_EXPORT CaptureModeController {
// Called when the feedback button on the capture bar is pressed. // Called when the feedback button on the capture bar is pressed.
void OpenFeedbackDialog(); void OpenFeedbackDialog();
// Skips the 3-second count down, and IsCaptureAllowed() checks, and starts
// video recording right away for testing purposes.
void StartVideoRecordingImmediatelyForTesting();
private: private:
// Returns true if doing a screen capture is currently allowed, false // Returns true if doing a screen capture is currently allowed, false
// otherwise. // otherwise.
...@@ -188,6 +193,9 @@ class ASH_EXPORT CaptureModeController { ...@@ -188,6 +193,9 @@ class ASH_EXPORT CaptureModeController {
// True when video recording is in progress. // True when video recording is in progress.
bool is_recording_in_progress_ = false; bool is_recording_in_progress_ = false;
// Watches events that lead to ending video recording.
std::unique_ptr<VideoRecordingWatcher> video_recording_watcher_;
// Timers used to schedule recording of the number of screenshots taken. // Timers used to schedule recording of the number of screenshots taken.
base::RepeatingTimer num_screenshots_taken_in_last_day_scheduler_; base::RepeatingTimer num_screenshots_taken_in_last_day_scheduler_;
base::RepeatingTimer num_screenshots_taken_in_last_week_scheduler_; base::RepeatingTimer num_screenshots_taken_in_last_week_scheduler_;
......
...@@ -237,6 +237,19 @@ class CaptureModeTest : public AshTestBase { ...@@ -237,6 +237,19 @@ class CaptureModeTest : public AshTestBase {
} }
} }
void RemoveSecondaryDisplay() {
const int64_t primary_id = WindowTreeHostManager::GetPrimaryDisplayId();
display::ManagedDisplayInfo primary_info =
display_manager()->GetDisplayInfo(primary_id);
std::vector<display::ManagedDisplayInfo> display_info_list;
display_info_list.push_back(primary_info);
display_manager()->OnNativeDisplaysChanged(display_info_list);
// Spin the run loop so that we get a signal that the associated root window
// of the removed display is destroyed.
base::RunLoop().RunUntilIdle();
}
private: private:
base::test::ScopedFeatureList scoped_feature_list_; base::test::ScopedFeatureList scoped_feature_list_;
}; };
...@@ -801,17 +814,7 @@ TEST_F(CaptureModeTest, DisplayRemoval) { ...@@ -801,17 +814,7 @@ TEST_F(CaptureModeTest, DisplayRemoval) {
.Contains(GetCaptureModeBarView()->GetBoundsInScreen())); .Contains(GetCaptureModeBarView()->GetBoundsInScreen()));
ASSERT_EQ(Shell::GetAllRootWindows()[1], session->current_root()); ASSERT_EQ(Shell::GetAllRootWindows()[1], session->current_root());
// Remove secondary display. RemoveSecondaryDisplay();
const int64_t primary_id = WindowTreeHostManager::GetPrimaryDisplayId();
display::ManagedDisplayInfo primary_info =
display_manager()->GetDisplayInfo(primary_id);
std::vector<display::ManagedDisplayInfo> display_info_list;
display_info_list.push_back(primary_info);
display_manager()->OnNativeDisplaysChanged(display_info_list);
// Spin the run loop so that we get a signal that the associated root window
// of the removed display is destroyed.
base::RunLoop().RunUntilIdle();
// Tests that the capture mode bar is now on the primary display. // Tests that the capture mode bar is now on the primary display.
EXPECT_TRUE(gfx::Rect(800, 800).Contains( EXPECT_TRUE(gfx::Rect(800, 800).Contains(
...@@ -1119,4 +1122,102 @@ TEST_F(CaptureModeTest, CaptureModeEntryPointHistograms) { ...@@ -1119,4 +1122,102 @@ TEST_F(CaptureModeTest, CaptureModeEntryPointHistograms) {
kTabletHistogram, CaptureModeEntryType::kAccelTakePartialScreenshot, 2); kTabletHistogram, CaptureModeEntryType::kAccelTakePartialScreenshot, 2);
} }
TEST_F(CaptureModeTest, ClosingWindowBeingRecorded) {
auto window = CreateTestWindow(gfx::Rect(200, 200));
StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseToCenterOf(window.get());
auto* controller = CaptureModeController::Get();
controller->StartVideoRecordingImmediatelyForTesting();
EXPECT_TRUE(controller->is_recording_in_progress());
// Closing the window being recorded should end video recording.
window.reset();
auto* stop_recording_button = Shell::GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->stop_recording_button_tray();
EXPECT_FALSE(stop_recording_button->visible_preferred());
EXPECT_FALSE(controller->is_recording_in_progress());
}
TEST_F(CaptureModeTest, DetachDisplayWhileWindowRecording) {
UpdateDisplay("400x400,401+0-400x400");
// Create a window on the second display.
auto window = CreateTestWindow(gfx::Rect(450, 20, 200, 200));
auto roots = Shell::GetAllRootWindows();
ASSERT_EQ(2u, roots.size());
EXPECT_EQ(window->GetRootWindow(), roots[1]);
StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
auto* event_generator = GetEventGenerator();
MoveMouseToAndUpdateCursorDisplay(window->GetBoundsInScreen().CenterPoint(),
event_generator);
auto* controller = CaptureModeController::Get();
controller->StartVideoRecordingImmediatelyForTesting();
EXPECT_TRUE(controller->is_recording_in_progress());
auto* stop_recording_button = RootWindowController::ForWindow(roots[1])
->GetStatusAreaWidget()
->stop_recording_button_tray();
EXPECT_TRUE(stop_recording_button->visible_preferred());
// Disconnecting the display, on which the window being recorded is located,
// should not end the recording. The window should be reparented to another
// display, and the stop-recording button should move with to that display.
RemoveSecondaryDisplay();
roots = Shell::GetAllRootWindows();
ASSERT_EQ(1u, roots.size());
EXPECT_TRUE(controller->is_recording_in_progress());
stop_recording_button = RootWindowController::ForWindow(roots[0])
->GetStatusAreaWidget()
->stop_recording_button_tray();
EXPECT_TRUE(stop_recording_button->visible_preferred());
}
TEST_F(CaptureModeTest, ClosingDisplayBeingFullscreenRecorded) {
UpdateDisplay("400x400,401+0-400x400");
auto roots = Shell::GetAllRootWindows();
ASSERT_EQ(2u, roots.size());
StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
auto* event_generator = GetEventGenerator();
MoveMouseToAndUpdateCursorDisplay(roots[1]->GetBoundsInScreen().CenterPoint(),
event_generator);
auto* controller = CaptureModeController::Get();
controller->StartVideoRecordingImmediatelyForTesting();
EXPECT_TRUE(controller->is_recording_in_progress());
auto* stop_recording_button = RootWindowController::ForWindow(roots[1])
->GetStatusAreaWidget()
->stop_recording_button_tray();
EXPECT_TRUE(stop_recording_button->visible_preferred());
// Disconnecting the display being fullscreen recorded should end the
// recording and remove the stop recording button.
RemoveSecondaryDisplay();
roots = Shell::GetAllRootWindows();
ASSERT_EQ(1u, roots.size());
EXPECT_FALSE(controller->is_recording_in_progress());
stop_recording_button = RootWindowController::ForWindow(roots[0])
->GetStatusAreaWidget()
->stop_recording_button_tray();
EXPECT_FALSE(stop_recording_button->visible_preferred());
}
TEST_F(CaptureModeTest, ShuttingDownWhileRecording) {
StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
auto* controller = CaptureModeController::Get();
controller->StartVideoRecordingImmediatelyForTesting();
EXPECT_TRUE(controller->is_recording_in_progress());
// Exiting the test now will shut down ash while recording is in progress,
// there should be no crashes when
// VideoRecordingWatcher::OnChromeTerminating() terminates the recording.
}
} // namespace ash } // namespace ash
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
#include "ash/capture_mode/capture_mode_util.h" #include "ash/capture_mode/capture_mode_util.h"
#include "ash/capture_mode/stop_recording_button_tray.h"
#include "ash/root_window_controller.h"
#include "base/check.h"
#include "base/notreached.h" #include "base/notreached.h"
#include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/rect.h"
...@@ -59,6 +62,28 @@ bool ShouldHideDragAffordance(FineTunePosition position) { ...@@ -59,6 +62,28 @@ bool ShouldHideDragAffordance(FineTunePosition position) {
IsCornerFineTunePosition(position); IsCornerFineTunePosition(position);
} }
void SetStopRecordingButtonVisibility(aura::Window* root, bool visible) {
DCHECK(root);
DCHECK(root->IsRootWindow());
// Recording can end when a display being fullscreen-captured gets removed, in
// this case, we don't need to hide the button.
if (root->is_destroying()) {
DCHECK(!visible);
return;
}
// Can be null while shutting down.
auto* root_window_controller = RootWindowController::ForWindow(root);
if (!root_window_controller)
return;
auto* stop_recording_button = root_window_controller->GetStatusAreaWidget()
->stop_recording_button_tray();
DCHECK(stop_recording_button);
stop_recording_button->SetVisiblePreferred(visible);
}
} // namespace capture_mode_util } // namespace capture_mode_util
} // namespace ash } // namespace ash
...@@ -8,6 +8,10 @@ ...@@ -8,6 +8,10 @@
#include "ash/ash_export.h" #include "ash/ash_export.h"
#include "ash/capture_mode/capture_mode_types.h" #include "ash/capture_mode/capture_mode_types.h"
namespace aura {
class Window;
} // namespace aura
namespace gfx { namespace gfx {
class Point; class Point;
class Rect; class Rect;
...@@ -22,10 +26,14 @@ ASH_EXPORT gfx::Point GetLocationForFineTunePosition(const gfx::Rect& rect, ...@@ -22,10 +26,14 @@ ASH_EXPORT gfx::Point GetLocationForFineTunePosition(const gfx::Rect& rect,
FineTunePosition position); FineTunePosition position);
// Return whether |position| is a corner. // Return whether |position| is a corner.
ASH_EXPORT bool IsCornerFineTunePosition(FineTunePosition position); bool IsCornerFineTunePosition(FineTunePosition position);
// Return whether drag affordance circles should be hidden. // Return whether drag affordance circles should be hidden.
ASH_EXPORT bool ShouldHideDragAffordance(FineTunePosition position); bool ShouldHideDragAffordance(FineTunePosition position);
// Sets the visibility of the stop-recording button in the Shelf's status area
// widget of the given |root| window.
void SetStopRecordingButtonVisibility(aura::Window* root, bool visible);
} // namespace capture_mode_util } // namespace capture_mode_util
......
...@@ -36,10 +36,8 @@ bool StopRecordingButtonTray::PerformAction(const ui::Event& event) { ...@@ -36,10 +36,8 @@ bool StopRecordingButtonTray::PerformAction(const ui::Event& event) {
DCHECK(event.type() == ui::ET_MOUSE_RELEASED || DCHECK(event.type() == ui::ET_MOUSE_RELEASED ||
event.type() == ui::ET_GESTURE_TAP); event.type() == ui::ET_GESTURE_TAP);
// Stop recording and hide this button.
base::RecordAction(base::UserMetricsAction("Tray_StopRecording")); base::RecordAction(base::UserMetricsAction("Tray_StopRecording"));
CaptureModeController::Get()->EndVideoRecording(); CaptureModeController::Get()->EndVideoRecording();
SetVisiblePreferred(false);
return true; return true;
} }
......
// Copyright 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 "ash/capture_mode/video_recording_watcher.h"
#include "ash/capture_mode/capture_mode_controller.h"
#include "ash/capture_mode/capture_mode_util.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/notreached.h"
#include "ui/aura/window.h"
namespace ash {
VideoRecordingWatcher::VideoRecordingWatcher(
CaptureModeController* controller,
aura::Window* window_being_recorded)
: controller_(controller), window_being_recorded_(window_being_recorded) {
DCHECK(controller_);
DCHECK(window_being_recorded_);
DCHECK(controller_->is_recording_in_progress());
window_being_recorded_->AddObserver(this);
Shell::Get()->session_controller()->AddObserver(this);
}
VideoRecordingWatcher::~VideoRecordingWatcher() {
DCHECK(window_being_recorded_);
window_being_recorded_->RemoveObserver(this);
Shell::Get()->session_controller()->RemoveObserver(this);
}
void VideoRecordingWatcher::OnWindowDestroying(aura::Window* window) {
DCHECK_EQ(window, window_being_recorded_);
DCHECK(controller_->is_recording_in_progress());
// EndVideoRecording() destroys |this|. No need to remove observer here, since
// it will be done in the destructor.
controller_->EndVideoRecording();
}
void VideoRecordingWatcher::OnWindowDestroyed(aura::Window* window) {
DCHECK_EQ(window, window_being_recorded_);
// We should never get here, since OnWindowDestroying() calls
// EndVideoRecording() which deletes us.
NOTREACHED();
}
void VideoRecordingWatcher::OnWindowRemovingFromRootWindow(
aura::Window* window,
aura::Window* new_root) {
DCHECK_EQ(window, window_being_recorded_);
DCHECK(controller_->is_recording_in_progress());
if (!new_root)
return;
// When a window being recorded changes displays either due to a display
// getting disconnected, or moved by the user, the stop-recording button
// should follow that window to that display.
capture_mode_util::SetStopRecordingButtonVisibility(window->GetRootWindow(),
false);
capture_mode_util::SetStopRecordingButtonVisibility(new_root, true);
}
void VideoRecordingWatcher::OnSessionStateChanged(
session_manager::SessionState state) {
DCHECK(controller_->is_recording_in_progress());
if (Shell::Get()->session_controller()->IsUserSessionBlocked())
controller_->EndVideoRecording();
}
void VideoRecordingWatcher::OnChromeTerminating() {
DCHECK(controller_->is_recording_in_progress());
controller_->EndVideoRecording();
}
} // namespace ash
// Copyright 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.
#ifndef ASH_CAPTURE_MODE_VIDEO_RECORDING_WATCHER_H_
#define ASH_CAPTURE_MODE_VIDEO_RECORDING_WATCHER_H_
#include "ash/public/cpp/session/session_observer.h"
#include "ui/aura/window_observer.h"
namespace ash {
class CaptureModeController;
// An instance of this class is created while video recording is in progress to
// watch for events that end video recording, such as a window being recorded
// gets closed, a display being fullscreen-recorded gets disconnected, the user
// session gets blocked, or while shutting down.
// TODO(https://crbug.com/1142994): Decide what to do when switching users in
// multi-profile.
// TODO(https://crbug.com/1145003): Use this to paint a border around the area
// being recorded while recording is in progress.
class VideoRecordingWatcher : public aura::WindowObserver,
public SessionObserver {
public:
VideoRecordingWatcher(CaptureModeController* controller,
aura::Window* window_being_recorded);
~VideoRecordingWatcher() override;
aura::Window* window_being_recorded() const { return window_being_recorded_; }
// aura::WindowObserver:
void OnWindowDestroying(aura::Window* window) override;
void OnWindowDestroyed(aura::Window* window) override;
void OnWindowRemovingFromRootWindow(aura::Window* window,
aura::Window* new_root) override;
// SessionObserver:
void OnSessionStateChanged(session_manager::SessionState state) override;
void OnChromeTerminating() override;
private:
CaptureModeController* const controller_;
aura::Window* const window_being_recorded_;
};
} // namespace ash
#endif // ASH_CAPTURE_MODE_VIDEO_RECORDING_WATCHER_H_
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