blob: 38ae036e953afb8257f0874eef034ec33eb53796 [file] [log] [blame]
// 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_CAPTURE_MODE_SESSION_H_
#define ASH_CAPTURE_MODE_CAPTURE_MODE_SESSION_H_
#include <memory>
#include <vector>
#include "ash/accessibility/magnifier/magnifier_glass.h"
#include "ash/ash_export.h"
#include "ash/capture_mode/capture_mode_toast_controller.h"
#include "ash/capture_mode/capture_mode_types.h"
#include "ash/capture_mode/folder_selection_dialog_controller.h"
#include "ash/public/cpp/tablet_mode_observer.h"
#include "ash/shell_observer.h"
#include "base/containers/flat_set.h"
#include "base/memory/weak_ptr.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/compositor/layer_delegate.h"
#include "ui/compositor/layer_owner.h"
#include "ui/display/display_observer.h"
#include "ui/events/event.h"
#include "ui/events/event_handler.h"
#include "ui/gfx/geometry/point.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"
namespace gfx {
class Canvas;
} // namespace gfx
namespace ash {
class CaptureModeBarView;
class CaptureModeController;
class CaptureModeSessionFocusCycler;
class CaptureModeSettingsView;
class CaptureWindowObserver;
class UserNudgeController;
class WindowDimmer;
// Encapsulates an active capture mode session (i.e. an instance of this class
// lives as long as capture mode is active). It creates and owns the capture
// mode bar widget.
// The CaptureModeSession is a LayerOwner that owns a texture layer placed right
// beneath the layer of the bar widget. This layer is used to paint a dimming
// shield of the areas that won't be captured, and another bright region showing
// the one that will be.
class ASH_EXPORT CaptureModeSession
: public ui::LayerOwner,
public ui::LayerDelegate,
public ui::EventHandler,
public TabletModeObserver,
public aura::WindowObserver,
public display::DisplayObserver,
public FolderSelectionDialogController::Delegate,
public ShellObserver {
public:
// Creates the bar widget on a calculated root window. |projector_mode|
// specifies whether this session was started for the projector workflow.
CaptureModeSession(CaptureModeController* controller, bool projector_mode);
CaptureModeSession(const CaptureModeSession&) = delete;
CaptureModeSession& operator=(const CaptureModeSession&) = delete;
~CaptureModeSession() override;
// The vertical distance from the size label to the custom capture region.
static constexpr int kSizeLabelYDistanceFromRegionDp = 8;
// The vertical distance of the capture button from the capture region, if the
// capture button does not fit inside the capture region.
static constexpr int kCaptureButtonDistanceFromRegionDp = 24;
aura::Window* current_root() const { return current_root_; }
views::Widget* capture_mode_bar_widget() {
return capture_mode_bar_widget_.get();
}
views::Widget* capture_label_widget() { return capture_label_widget_.get(); }
views::Widget* capture_mode_settings_widget() {
return capture_mode_settings_widget_.get();
}
bool is_in_projector_mode() const { return is_in_projector_mode_; }
void set_can_exit_on_escape(bool value) { can_exit_on_escape_ = value; }
bool is_selecting_region() const { return is_selecting_region_; }
bool is_drag_in_progress() const { return is_drag_in_progress_; }
void set_a11y_alert_on_session_exit(bool value) {
a11y_alert_on_session_exit_ = value;
}
bool is_shutting_down() const { return is_shutting_down_; }
void set_is_stopping_to_start_video_recording(bool value) {
is_stopping_to_start_video_recording_ = value;
}
CaptureModeToastController* capture_toast_controller() {
return &capture_toast_controller_;
}
// Initializes the capture mode session. This should be called right after the
// object is created.
void Initialize();
// Shuts down the capture mode session. This should be called right before the
// object is destroyed.
void Shutdown();
// Gets the current window selected for |kWindow| capture source. Returns
// nullptr if no window is available for selection.
aura::Window* GetSelectedWindow() const;
// Called when a user toggles the capture source or capture type to announce
// an accessibility alert. If `trigger_now` is true, it will announce
// immediately; otherwise, it will trigger another alert asynchronously with
// the alert.
void A11yAlertCaptureSource(bool trigger_now);
// Called when switching a capture type from another capture type.
void A11yAlertCaptureType();
// Called when either the capture source or type changes.
void OnCaptureSourceChanged(CaptureModeSource new_source);
void OnCaptureTypeChanged(CaptureModeType new_type);
// When performing capture, or at the end of the 3-second count down, the DLP
// manager is checked for any restricted content. The DLP manager may choose
// to show a system-modal dialog to warn the user about some content they're
// about to capture. This function is called to prepare for this case by
// hiding all the capture mode UIs and stopping the consumption of input
// events, so users can interact with the dialog.
void OnWaitingForDlpConfirmationStarted();
// This function is called when the DLP manager replies back. If `reshow_uis`
// is true, then we'll undo the hiding of all the capture mode UIs done in
// OnWaitingForDlpConfirmationStarted().
void OnWaitingForDlpConfirmationEnded(bool reshow_uis);
// Called when the settings menu is toggled.
void SetSettingsMenuShown(bool shown);
// Called when the user performs a capture. Records histograms related to this
// session.
void ReportSessionHistograms();
// Called when starting 3-seconds count down before recording video.
void StartCountDown(base::OnceClosure countdown_finished_callback);
// Opens the dialog that lets users pick the folder to which they want the
// captured files to be saved.
void OpenFolderSelectionDialog();
// Returns true if we are currently in video recording countdown animation.
bool IsInCountDownAnimation() const;
// Called when the capture folder may have changed to update the set of menu
// options in the settings menu and resize it so that it fits its potentially
// new contents.
void OnCaptureFolderMayHaveChanged();
// Called when we change the setting to force-use the default downloads folder
// as the save folder.
void OnDefaultCaptureFolderSelectionChanged();
// Returns the current parent window for
// `CaptureModeCameraController::camera_preview_widget_` when capture mode
// session is active.
aura::Window* GetCameraPreviewParentWindow() const;
// Returns the confine bounds for the camera preview when capture session is
// active.
gfx::Rect GetCameraPreviewConfineBounds() const;
// Returns the in-session target value that should be used for the visibility
// of the camera preview (if any). During the session, things like dragging
// the user region may affect the camera preview's visibility, and hence this
// function should be consulted.
bool CalculateCameraPreviewTargetVisibility() const;
// ui::LayerDelegate:
void OnPaintLayer(const ui::PaintContext& context) override;
void OnDeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor) override {}
// ui::EventHandler:
void OnKeyEvent(ui::KeyEvent* event) override;
void OnMouseEvent(ui::MouseEvent* event) override;
void OnTouchEvent(ui::TouchEvent* event) override;
// TabletModeObserver:
void OnTabletModeStarted() override;
void OnTabletModeEnded() override;
// aura::WindowObserver:
void OnWindowDestroying(aura::Window* window) override;
// ShellObserver:
void OnRootWindowWillShutdown(aura::Window* root_window) override;
// display::DisplayObserver:
void OnDisplayMetricsChanged(const display::Display& display,
uint32_t metrics) override;
// FolderSelectionDialogController::Delegate:
void OnFolderSelected(const base::FilePath& path) override;
void OnSelectionWindowAdded() override;
void OnSelectionWindowClosed() override;
// Updates the current cursor depending on current |location_in_screen| and
// current capture type and source. |is_touch| is used when calculating fine
// tune position in region capture mode. We'll have a larger hit test region
// for the touch events than the mouse events.
void UpdateCursor(const gfx::Point& location_in_screen, bool is_touch);
// Highlights the give |window| for keyboard navigation
// events (tabbing through windows in capture window mode).
void HighlightWindowForTab(aura::Window* window);
// Called when the settings view has been updated, its bounds may need to be
// updated correspondingly.
void MaybeUpdateSettingsBounds();
// Called when opacity of capture UIs (capture bar, capture label) may need to
// be updated. For example, when camera preview is created, destroyed,
// reparented, display metrics change or located events enter / exit / move
// on capture UI.
void MaybeUpdateCaptureUisOpacity(
absl::optional<gfx::Point> cursor_screen_location = absl::nullopt);
void OnCameraPreviewDragStarted();
void OnCameraPreviewDragEnded(const gfx::Point& screen_location,
bool is_touch);
// Called every time when camera preview is updated.
// `capture_surface_became_too_small` indicates whether the camera preview
// becomes invisible is due to the capture surface becoming too small.
// `did_bounds_or_visibility_change` determines whether the capture UIs'
// opacity should be updated.
void OnCameraPreviewBoundsOrVisibilityChanged(
bool capture_surface_became_too_small,
bool did_bounds_or_visibility_change);
void OnCameraPreviewDestroyed();
private:
friend class CaptureModeSettingsTestApi;
friend class CaptureModeSessionFocusCycler;
friend class CaptureModeSessionTestApi;
friend class CaptureModeTestApi;
class CursorSetter;
class ParentContainerObserver;
enum class CaptureLabelAnimation {
// No animation on the capture label.
kNone,
// The animation on the capture label when the user has finished selecting a
// region and is moving to the fine tune phase.
kRegionPhaseChange,
// The animation on the capture label when the user has clicked record and
// the capture label animates into a countdown label.
kCountdownStart,
};
// Returns a list of all the currently available widgets that are owned by
// this session.
std::vector<views::Widget*> GetAvailableWidgets();
// All UI elements, cursors, widgets and paintings on the session's layer can
// be either shown or hidden with the below functions.
void HideAllUis();
void ShowAllUis();
// Called by `ShowAllUis` for each widget. Returns true if the given `widget`
// could be shown, otherwise, returns false.
bool CanShowWidget(views::Widget* widget) const;
// Sets the correct screen bounds on the `capture_mode_bar_widget_` based on
// the `current_root_`, potentially moving the bar to a new display if
// `current_root_` is different`.
void RefreshBarWidgetBounds();
// If possible, this recreates and shows the nudge that alerts the user about
// the new folder selection settings. The nudge will be created on top of the
// the settings button on the capture mode bar.
void MaybeCreateUserNudge();
// If there's a user nudge currently showing, it will be dismissed forever,
// and will no longer be shown to the user.
void MaybeDismissUserNudgeForever();
// Called to accept and trigger a capture operation. This happens e.g. when
// the user hits enter, selects a window/display to capture, or presses on the
// record button in the capture label view.
void DoPerformCapture();
// Gets the bounds of current window selected for |kWindow| capture source.
gfx::Rect GetSelectedWindowBounds() const;
// Ensures that the bar widget is on top of everything, and the overlay (which
// is the |layer()| of this class that paints the capture region) is stacked
// below the bar.
void RefreshStackingOrder();
// Paints the current capture region depending on the current capture source.
void PaintCaptureRegion(gfx::Canvas* canvas);
// Helper to unify mouse/touch events. Forwards events to the three below
// functions and they are located on |capture_button_widget_|. Blocks events
// from reaching other handlers, unless the event is located on
// |capture_mode_bar_widget_|. |is_touch| signifies this is a touch event, and
// we will use larger hit targets for the drag affordances.
void OnLocatedEvent(ui::LocatedEvent* event, bool is_touch);
// Returns the fine tune position that corresponds to the given
// `location_in_screen`.
FineTunePosition GetFineTunePosition(const gfx::Point& location_in_screen,
bool is_touch) const;
// Handles updating the select region UI.
void OnLocatedEventPressed(const gfx::Point& location_in_root, bool is_touch);
void OnLocatedEventDragged(const gfx::Point& location_in_root);
void OnLocatedEventReleased(const gfx::Point& location_in_root);
// Updates the capture region and the capture region widgets depending on the
// value of |is_resizing|. |by_user| is true if the capture region is changed
// by user.
void UpdateCaptureRegion(const gfx::Rect& new_capture_region,
bool is_resizing,
bool by_user);
// Updates the dimensions label widget shown during a region capture session.
// If not |is_resizing|, not a region capture session or the capture region is
// empty, clear existing |dimensions_label_widget_|. Otherwise, create and
// update the dimensions label.
void UpdateDimensionsLabelWidget(bool is_resizing);
// Updates the bounds of |dimensions_label_widget_| relative to the current
// capture region. Both |dimensions_label_widget_| and its content view must
// exist.
void UpdateDimensionsLabelBounds();
// If |fine_tune_position_| is not a corner, do nothing. Otherwise show
// |magnifier_glass_| at |location_in_root| in the current root window and
// hide the cursor.
void MaybeShowMagnifierGlassAtPoint(const gfx::Point& location_in_root);
// Closes |magnifier_glass_|.
void CloseMagnifierGlass();
// Retrieves the anchor points on the current selected region associated with
// |position|. The anchor points are described as the points that do not
// change when resizing the capture region while dragging one of the drag
// affordances. There is one anchor point if |position| is a vertex, and two
// anchor points if |position| is an edge.
std::vector<gfx::Point> GetAnchorPointsForPosition(FineTunePosition position);
// Updates the capture label widget's icon/text and bounds. The capture label
// widget may be animated depending on |animation_type|.
void UpdateCaptureLabelWidget(CaptureLabelAnimation animation_type);
// Updates the capture label widget's bounds. The capture label
// widget may be animated depending on |animation_type|.
void UpdateCaptureLabelWidgetBounds(CaptureLabelAnimation animation_type);
// Calculates the targeted capture label widget bounds in screen coordinates.
gfx::Rect CalculateCaptureLabelWidgetBounds();
// Returns true if the capture label should handle the event. |event_target|
// is the window which is receiving the event. The capture label should handle
// the event if its associated window is |event_target| and its capture button
// child is visible.
bool ShouldCaptureLabelHandleEvent(aura::Window* event_target);
// Handles changing |root_window_| when the mouse cursor changes to another
// display, or if a display was removed. Moves the capture mode widgets to
// |new_root| depending on the capture mode source an whether it was a display
// removal.
void MaybeChangeRoot(aura::Window* new_root);
// Updates |root_window_dimmers_| to dim the correct root windows.
void UpdateRootWindowDimmers();
// Returns true if we're using custom image capture icon when |type| is
// kImage or using custom video capture icon when |type| is kVideo.
bool IsUsingCustomCursor(CaptureModeType type) const;
// Ensure the user region in |controller_| is within the bounds of the root
// window. This is called when creating |this| or when the display bounds have
// changed.
void ClampCaptureRegionToRootWindowSize();
// Ends a region selection. Cleans up internal state and updates the cursor,
// capture UIs' opacity and magnifier glass. The `cursor_screen_location`
// could not be provided in some use cases, for example the capture region is
// updated because of the display metrics are changed. When
// `cursor_screen_location` is not provived, we will try to get the screen
// location of the mouse.
void EndSelection(
absl::optional<gfx::Point> cursor_screen_location = absl::nullopt);
// Schedules a paint on the region and enough inset around it so that the
// shadow, affordance circles, etc. are all repainted.
void RepaintRegion();
// Selects a default region that is centered and whose size is a ratio of the
// root window bounds. Called when the space key is pressed.
void SelectDefaultRegion();
// Updates the region either horizontally or vertically. Called when the arrow
// keys are pressed. |event_flags| are the flags from the event that triggers
// these calls. Different modifiers will move the region more or less.
void UpdateRegionHorizontally(bool left, int event_flags);
void UpdateRegionVertically(bool up, int event_flags);
// Called when the parent container of camera preview may need to be updated.
void MaybeReparentCameraPreviewWidget();
// Called at the beginning or end of the drag of capture region to update the
// camera preview's bounds and visibility.
void MaybeUpdateCameraPreviewBounds();
// Returns true if the given `event` is targeted on the capture bar.
bool IsEventTargetedOnCaptureBar(const ui::LocatedEvent& event) const;
// Returns true if the given `event` is targeted on the setting menu if it
// exists.
bool IsEventTargetedOnSettingsMenu(const ui::LocatedEvent& event) const;
CaptureModeController* const controller_;
// The current root window on which the capture session is active, which may
// change if the user warps the cursor to another display in some situations.
aura::Window* current_root_;
views::UniqueWidgetPtr capture_mode_bar_widget_ =
std::make_unique<views::Widget>();
// The content view of the above widget and owned by its views hierarchy.
CaptureModeBarView* capture_mode_bar_view_ = nullptr;
views::UniqueWidgetPtr capture_mode_settings_widget_;
// The content view of the above widget and owned by its views hierarchy.
CaptureModeSettingsView* capture_mode_settings_view_ = nullptr;
// Widget which displays capture region size during a region capture session.
views::UniqueWidgetPtr dimensions_label_widget_;
// Widget that shows an optional icon and a message in the middle of the
// screen or in the middle of the capture region and prompts the user what to
// do next. The icon and message can be different in different capture type
// and source and can be empty in some cases. And in video capture mode, when
// starting capturing, the widget will transform into a 3-second countdown
// timer.
views::UniqueWidgetPtr capture_label_widget_;
// Magnifier glass used during a region capture session.
MagnifierGlass magnifier_glass_;
// True if all UIs (cursors, widgets, and paintings on the layer) of the
// capture mode session is visible.
bool is_all_uis_visible_ = true;
// Whether this session was started from a projector workflow.
const bool is_in_projector_mode_ = false;
// Whether pressing the escape key can exit the session. This is used when we
// find capturable content at the end of the 3-second count down, but we need
// to do some extra asynchronous operations before we start the actual
// recording. At this point we don't want the user to be able to bail out.
bool can_exit_on_escape_ = true;
// Stores the data needed to select a region during a region capture session.
// This variable indicates if the user is currently selecting a region to
// capture, it will be true when the first mouse/touch presses down and will
// remain true until the mouse/touch releases up. After that, if the capture
// region is non empty, the capture session will enter the fine tune phase,
// where the user can reposition and resize the region with a lot of accuracy.
bool is_selecting_region_ = false;
// True when a located pressed event is received outside the bounds of a
// present settings menu widget. This event will be used to dismiss the
// settings menu and all future located events up to and including the
// released event will be ignored (i.e. will not be used to update the capture
// region, perform capture ... etc.).
bool ignore_located_events_ = false;
// The location of the last press and drag events.
gfx::Point initial_location_in_root_;
gfx::Point previous_location_in_root_;
// The position of the last press event during the fine tune phase drag.
FineTunePosition fine_tune_position_ = FineTunePosition::kNone;
// The points that do not change during a fine tune resize. This is empty
// when |fine_tune_position_| is kNone or kCenter, or if there is no drag
// underway.
std::vector<gfx::Point> anchor_points_;
// Caches the old status of mouse warping while dragging or resizing a
// captured region.
absl::optional<bool> old_mouse_warp_status_;
// Observer to observe the current selected to-be-captured window.
std::unique_ptr<CaptureWindowObserver> capture_window_observer_;
// Observer to observe the parent container `kShellWindowId_MenuContainer`.
std::unique_ptr<ParentContainerObserver> parent_container_observer_;
// Contains the window dimmers which dim all the root windows except
// |current_root_|.
base::flat_set<std::unique_ptr<WindowDimmer>> root_window_dimmers_;
// The object to specify the cursor type.
std::unique_ptr<CursorSetter> cursor_setter_;
// True when dragging is in progress.
bool is_drag_in_progress_ = false;
// Counter used to track the number of times a user adjusts a capture region.
// This should be reset when a user creates a new capture region, changes
// capture sources or when a user performs a capture.
int num_capture_region_adjusted_ = 0;
// True if at any point during the lifetime of |this|, the capture source
// changed. Used for metrics collection.
bool capture_source_changed_ = false;
// The window which had input capture prior to entering the session. It may be
// null if no such window existed.
aura::Window* input_capture_window_ = nullptr;
// False only when we end the session to start recording.
bool a11y_alert_on_session_exit_ = true;
// The display observer between init/shutdown.
absl::optional<display::ScopedDisplayObserver> display_observer_;
// True once Shutdown() is called.
bool is_shutting_down_ = false;
// True when the session is being stopped to start video recording, at which
// point, it's guaranteed that recording will start and will not be blocked by
// any errors, DLP restrictions, or any user cancellation.
bool is_stopping_to_start_video_recording_ = false;
// True when we ask the DLP manager to check the screen content before we
// perform the capture.
bool is_waiting_for_dlp_confirmation_ = false;
// The object which handles tab focus while in a capture session.
std::unique_ptr<CaptureModeSessionFocusCycler> focus_cycler_;
// True if a located event should be passed to camera preview to be handled.
bool should_pass_located_event_to_camera_preview_ = false;
// Controls the folder selection dialog. Not null only while the dialog is
// shown.
std::unique_ptr<FolderSelectionDialogController>
folder_selection_dialog_controller_;
// Controls the user nudge animations.
std::unique_ptr<UserNudgeController> user_nudge_controller_;
// Controls creating, destroying or updating the visibility of the capture
// toast.
CaptureModeToastController capture_toast_controller_;
base::WeakPtrFactory<CaptureModeSession> weak_ptr_factory_{this};
};
} // namespace ash
#endif // ASH_CAPTURE_MODE_CAPTURE_MODE_SESSION_H_