chromium/ash/capture_mode/capture_mode_session.h

// Copyright 2020 The Chromium Authors
// 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 <optional>
#include <vector>

#include "ash/accessibility/magnifier/magnifier_glass.h"
#include "ash/capture_mode/base_capture_mode_session.h"
#include "ash/capture_mode/capture_label_view.h"
#include "ash/capture_mode/capture_mode_behavior.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/shell_observer.h"
#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "ui/aura/window_observer.h"
#include "ui/color/color_provider_source_observer.h"
#include "ui/compositor/layer_delegate.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/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"

namespace display {
enum class TabletState;
}  // namespace display

namespace gfx {
class Canvas;
}  // namespace gfx

namespace ash {

class CaptureModeBarView;
class CaptureModeController;
class CaptureModeSessionFocusCycler;
class CaptureModeSettingsView;
class CaptureWindowObserver;
class CursorSetter;
class RecordingTypeMenuView;
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 BaseCaptureModeSession,
      public ui::LayerDelegate,
      public ui::EventHandler,
      public aura::WindowObserver,
      public display::DisplayObserver,
      public FolderSelectionDialogController::Delegate,
      public ui::ColorProviderSourceObserver {
 public:
  // Centralized place to control the events, observe windows and create the
  // capture mode needed widgets including `capture_mode_bar_widget_`,
  // `capture_label_widget_`, `recording_type_menu_widget_`, etc, on a
  // calculated root window. `active_behavior` will customize the widgets or
  // restrict certain operations.
  CaptureModeSession(CaptureModeController* controller,
                     CaptureModeBehavior* active_behavior);
  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;

  views::Widget* capture_label_widget() { return capture_label_widget_.get(); }
  views::Widget* capture_mode_settings_widget() {
    return capture_mode_settings_widget_.get();
  }
  views::Widget* search_results_panel_widget() {
    return search_results_panel_widget_.get();
  }
  bool is_selecting_region() const { return is_selecting_region_; }
  CaptureModeToastController* capture_toast_controller() {
    return &capture_toast_controller_;
  }

  // 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 the settings menu is toggled. If `by_key_event` is true, it
  // means that the settings menu is being opened or closed as a result of a key
  // event (e.g. pressing the space bar) on the settings button.
  void SetSettingsMenuShown(bool shown, bool by_key_event = false);

  // 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;

  // Returns true if `capture_window_observer_` exists and the capture bar is
  // anchored to a pre-selected window.
  bool IsBarAnchoredToWindow() const;

  // 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(
      std::optional<gfx::Point> cursor_screen_location = std::nullopt);

  // 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();

  // BaseCaptureModeSession:
  views::Widget* GetCaptureModeBarWidget() override;
  aura::Window* GetSelectedWindow() const override;
  void SetPreSelectedWindow(aura::Window* pre_selected_window) override;
  void OnCaptureSourceChanged(CaptureModeSource new_source) override;
  void OnCaptureTypeChanged(CaptureModeType new_type) override;
  void OnRecordingTypeChanged() override;
  void OnAudioRecordingModeChanged() override;
  void OnDemoToolsSettingsChanged() override;
  void OnWaitingForDlpConfirmationStarted() override;
  void OnWaitingForDlpConfirmationEnded(bool reshow_uis) override;
  void ReportSessionHistograms() override;
  void StartCountDown(base::OnceClosure countdown_finished_callback) override;
  void OnCaptureFolderMayHaveChanged() override;
  void OnDefaultCaptureFolderSelectionChanged() override;
  bool CalculateCameraPreviewTargetVisibility() const override;
  void OnCameraPreviewDragStarted() override;
  void OnCameraPreviewDragEnded(const gfx::Point& screen_location,
                                bool is_touch) override;
  void OnCameraPreviewBoundsOrVisibilityChanged(
      bool capture_surface_became_too_small,
      bool did_bounds_or_visibility_change) override;
  void OnCameraPreviewDestroyed() override;
  void MaybeDismissUserNudgeForever() override;
  void MaybeChangeRoot(aura::Window* new_root,
                       bool root_window_will_shutdown) override;
  std::set<aura::Window*> GetWindowsToIgnoreFromWidgets() override;
  void ShowSearchResultsPanel(const gfx::ImageSkia& image) override;

  // 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;

  // aura::WindowObserver:
  void OnWindowDestroying(aura::Window* window) override;

  // display::DisplayObserver:
  void OnDisplayTabletStateChanged(display::TabletState state) override;
  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;

  // ui::ColorProviderSourceObserver:
  void OnColorProviderChanged() override;

 private:
  friend class CaptureModeSettingsTestApi;
  friend class CaptureModeSessionFocusCycler;
  friend class CaptureModeSessionTestApi;
  friend class CaptureModeTestApi;
  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,
  };

  // Called when switching a capture type from another capture type.
  void A11yAlertCaptureType();

  // 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;

  // 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();

  // 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();

  // Called when the drop-down button in the `capture_label_widget_` is pressed
  // which toggles the recording type menu on and off.
  void OnRecordingTypeDropDownButtonPressed(const ui::Event& event);

  // 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);

  // 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(
      std::optional<gfx::Point> cursor_screen_location = std::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 UpdateRegionForArrowKeys(ui::KeyboardCode key_code, 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();

  // Creates or distroys the recording type menu widget based on the given
  // `shown` value. If `by_key_event` is true, it means that the recording type
  // menu is being opened or closed as a result of a key event (e.g. pressing
  // the space bar) on the recording type drop down button.
  void SetRecordingTypeMenuShown(bool shown, bool by_key_event = false);

  // Returns true if the given `screen_location` is on the drop down button in
  // the `capture_label_widget_` which when clicked opens the recording type
  // menu.
  bool IsPointOnRecordingTypeDropDownButton(
      const gfx::Point& screen_location) const;

  // Updates the availability or bounds of the recording type menu widget
  // according to the current state.
  void MaybeUpdateRecordingTypeMenu();

  // Returns true if there is a selected window and it is the topmost
  // capturable window at `screen_point`. Returns false otherwise.
  bool IsPointOverSelectedWindow(const gfx::Point& screen_point) const;

  // BaseCaptureModeSession:
  void InitInternal() override;
  void ShutdownInternal() override;

  views::UniqueWidgetPtr capture_mode_bar_widget_ =
      std::make_unique<views::Widget>();

  // The content view of the above widget and owned by its views hierarchy.
  raw_ptr<CaptureModeBarView, DanglingUntriaged> capture_mode_bar_view_ =
      nullptr;

  views::UniqueWidgetPtr capture_mode_settings_widget_;

  // The content view of the above widget and owned by its views hierarchy.
  raw_ptr<CaptureModeSettingsView, DanglingUntriaged>
      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_;
  raw_ptr<CaptureLabelView, DanglingUntriaged> capture_label_view_ = nullptr;

  // Widget that hosts the recording type menu, from which the user can pick the
  // desired recording format type.
  views::UniqueWidgetPtr recording_type_menu_widget_;
  raw_ptr<RecordingTypeMenuView, DanglingUntriaged> recording_type_menu_view_ =
      nullptr;

  // Contains `SearchResultsPanel` as its contents view.
  // TODO(b/362772923): Determine whether we need to move
  // `search_results_panel_widget_` to `CaptureModeController`.
  std::unique_ptr<views::Widget> search_results_panel_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;

  // 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.
  std::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_;

  // 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.
  raw_ptr<aura::Window, DanglingUntriaged> input_capture_window_ = nullptr;

  // The display observer between init/shutdown.
  std::optional<display::ScopedDisplayObserver> display_observer_;

  // 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_