| // Copyright 2022 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_CAMERA_CONTROLLER_H_ |
| #define ASH_CAPTURE_MODE_CAPTURE_MODE_CAMERA_CONTROLLER_H_ |
| |
| #include <string> |
| #include <vector> |
| |
| #include "ash/ash_export.h" |
| #include "ash/capture_mode/capture_mode_types.h" |
| #include "ash/public/cpp/system_tray_observer.h" |
| #include "base/callback_forward.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/observer_list.h" |
| #include "base/observer_list_types.h" |
| #include "base/system/system_monitor.h" |
| #include "base/timer/timer.h" |
| #include "media/base/video_facing.h" |
| #include "media/capture/video/video_capture_device_info.h" |
| #include "media/capture/video_capture_types.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "services/video_capture/public/mojom/video_source_provider.mojom.h" |
| #include "ui/views/widget/unique_widget_ptr.h" |
| |
| namespace gfx { |
| class Rect; |
| } // namespace gfx |
| |
| namespace ash { |
| |
| class CameraPreviewView; |
| class CaptureModeDelegate; |
| |
| // The ID used internally in capture mode to identify the camera. |
| class ASH_EXPORT CameraId { |
| public: |
| CameraId() = default; |
| CameraId(std::string model_id, int number); |
| CameraId(const CameraId&) = default; |
| CameraId(CameraId&&) = default; |
| CameraId& operator=(const CameraId&) = default; |
| CameraId& operator=(CameraId&&) = default; |
| ~CameraId() = default; |
| |
| bool is_valid() const { return !model_id_or_display_name_.empty(); } |
| const std::string& model_id_or_display_name() const { |
| return model_id_or_display_name_; |
| } |
| int number() const { return number_; } |
| |
| bool operator==(const CameraId& rhs) const { |
| return model_id_or_display_name_ == rhs.model_id_or_display_name_ && |
| number_ == rhs.number_; |
| } |
| bool operator!=(const CameraId& rhs) const { return !(*this == rhs); } |
| |
| bool operator<(const CameraId& rhs) const; |
| |
| std::string ToString() const; |
| |
| private: |
| // A unique hardware ID of the camera device in the form of |
| // "[Vendor ID]:[Product ID]" (e.g. "0c45:6713"). Note that if multiple |
| // cameras from the same vendor and of the same model are connected to the |
| // device, they will all have the same `model_id`. |
| // Note that in some cases, `media::VideoCaptureDeviceDescriptor::model_id` |
| // may not be present. In this case, this will be filled by the camera's |
| // display name. |
| std::string model_id_or_display_name_; |
| |
| // A number that disambiguates cameras of the same type. For example if we |
| // have two connected cameras of the same type, the first one will have |
| // `number` set to 1, and the second's will be 2. |
| int number_ = 0; |
| }; |
| |
| struct CameraInfo { |
| CameraInfo(CameraId camera_id, |
| std::string device_id, |
| std::string display_name, |
| const media::VideoCaptureFormats& supported_formats, |
| media::VideoFacingMode camera_facing_mode); |
| CameraInfo(CameraInfo&&); |
| CameraInfo& operator=(CameraInfo&&); |
| ~CameraInfo(); |
| |
| // The ID used to identify the camera device internally to the capture mode |
| // code, which should be more stable than the below `device_id` which may |
| // change multiple times for the same camera. |
| CameraId camera_id; |
| |
| // The ID of the camera device given to it by the system in its current |
| // connection instance (e.g. "/dev/video2"). Note that the same camera device |
| // can disconnect and reconnect with a different `device_id` (e.g. when the |
| // cable is flaky). This ID is used to identify the camera to the video source |
| // provider in the video capture service. |
| std::string device_id; |
| |
| // The name of the camera device as shown to the end user (e.g. "Integrated |
| // Webcam"). |
| std::string display_name; |
| |
| // A list of supported capture formats by this camera. This list is sorted |
| // (See `media::VideoCaptureSystemImpl::DevicesInfoReady()`) by the frame size |
| // area, then by frame width, then by the *largest* frame rate. |
| media::VideoCaptureFormats supported_formats; |
| |
| // Whether the camera is facing the user (e.g. for internal front cameras), or |
| // the environment (e.g. internal rear cameras), or unknown (e.g. usually for |
| // external USB cameras). |
| media::VideoFacingMode camera_facing_mode; |
| }; |
| |
| using CameraInfoList = std::vector<CameraInfo>; |
| |
| // Controls detecting camera devices additions and removals and keeping a list |
| // of all currently connected cameras to the device. It also tracks all the |
| // capture mode selfie camera settings. |
| class ASH_EXPORT CaptureModeCameraController |
| : public base::SystemMonitor::DevicesChangedObserver, |
| public SystemTrayObserver { |
| public: |
| class Observer : public base::CheckedObserver { |
| public: |
| // Called to notify the observer that the list of `available_cameras_` has |
| // changed, and provides that list as `cameras`. |
| virtual void OnAvailableCamerasChanged(const CameraInfoList& cameras) = 0; |
| |
| // Called to notify the observer that a camera with `camera_id` was selected |
| // and will be used to show a camera preview when possible. |
| // Note that when `camera_id.is_valid()` is false, it means no camera is |
| // currently selected. |
| virtual void OnSelectedCameraChanged(const CameraId& camera_id) = 0; |
| |
| protected: |
| ~Observer() override = default; |
| }; |
| |
| explicit CaptureModeCameraController(CaptureModeDelegate* delegate); |
| CaptureModeCameraController(const CaptureModeCameraController&) = delete; |
| CaptureModeCameraController& operator=(const CaptureModeCameraController&) = |
| delete; |
| ~CaptureModeCameraController() override; |
| |
| const CameraInfoList& available_cameras() const { return available_cameras_; } |
| const CameraId& selected_camera() const { return selected_camera_; } |
| views::Widget* camera_preview_widget() const { |
| return camera_preview_widget_.get(); |
| } |
| CameraPreviewView* camera_preview_view() const { |
| return camera_preview_view_; |
| } |
| bool should_show_preview() const { return should_show_preview_; } |
| CameraPreviewSnapPosition camera_preview_snap_position() const { |
| return camera_preview_snap_position_; |
| } |
| bool is_drag_in_progress() const { return is_drag_in_progress_; } |
| bool is_camera_preview_collapsed() const { |
| return is_camera_preview_collapsed_; |
| } |
| |
| void AddObserver(Observer* observer); |
| void RemoveObserver(Observer* observer); |
| |
| // Selects the first camera in the `available_cameras_` list (if any), and |
| // only if no other camera is already selected. |
| void MaybeSelectFirstCamera(); |
| |
| // Returns true if camera support is disabled by admins via |
| // the `SystemFeaturesDisableList` policy, false otherwise. |
| bool IsCameraDisabledByPolicy() const; |
| |
| // Returns the display name of `selected_camera_`. Returns an empty string if |
| // the selected camera is not set. |
| std::string GetDisplayNameOfSelectedCamera() const; |
| |
| // Sets the currently selected camera to the whose ID is the given |
| // `camera_id`. If `camera_id` is invalid (see CameraId::is_valid()), this |
| // clears the selected camera. |
| void SetSelectedCamera(CameraId camera_id); |
| |
| // Sets `should_show_preview_` to the given `value`, and refreshes the state |
| // of the camera preview. |
| void SetShouldShowPreview(bool value); |
| |
| // Updates the parent of the `camera_preview_widget_` when necessary. E.g, |
| // capture source type changes, selected recording window changes etc. |
| void MaybeReparentPreviewWidget(); |
| |
| // Sets `camera_preview_snap_position_` and updates the preview widget's |
| // bounds accordingly. If `animate` is set to true, the camera preview will |
| // animate to its new snap position. |
| void SetCameraPreviewSnapPosition(CameraPreviewSnapPosition value, |
| bool animate = false); |
| |
| // Updates the bounds and visibility of `camera_preview_widget_` according to |
| // the current state of the capture surface within which the camera preview |
| // is confined and snapped to one of its corners. If `animate` is set to true, |
| // the widget will animate to the new target bounds. |
| void MaybeUpdatePreviewWidget(bool animate = false); |
| |
| // Handles drag events forwarded from `camera_preview_view_`. |
| void StartDraggingPreview(const gfx::PointF& screen_location); |
| void ContinueDraggingPreview(const gfx::PointF& screen_location); |
| void EndDraggingPreview(const gfx::PointF& screen_location, bool is_touch); |
| |
| // Updates the bounds of the preview widget and the value of |
| // `is_camera_preview_collapsed_` when the resize button is pressed. |
| void ToggleCameraPreviewSize(); |
| |
| // Called when a capture session gets started so we can refresh the cameras |
| // list, since the cros-camera service might have not been running when we |
| // tried to refresh the cameras at the beginning. (See |
| // http://b/230917107#comment12 for more details). |
| void OnCaptureSessionStarted(); |
| |
| void OnRecordingStarted(bool is_in_projector_mode); |
| void OnRecordingEnded(); |
| |
| // Called when the `CameraVideoFrameHandler` of the current |
| // `camera_preview_widget_` encounters a fatal error. This is considered a |
| // camera disconnection, and sometimes doesn't get reported via |
| // `OnDevicesChanged()` below, or may get delayed a lot. We manually remove |
| // the current camera from `available_cameras_`, delete its preview, and |
| // request a new list of cameras from the video capture service. |
| // https://crbug/1316230. |
| void OnFrameHandlerFatalError(); |
| |
| // Called when the device is shutting down. After this call, we don't do any |
| // operations that interacts with the video capture service. |
| void OnShuttingDown(); |
| |
| // As `camera_preview_view_` is a |
| // CaptureModeSessionFocusCycler::HighlightableView. This will show the focus |
| // ring and trigger setting a11y focus on the camera preview. Note, this is |
| // only for focusing the preview while recording is in progress. |
| void PseudoFocusCameraPreview(); |
| |
| void OnActiveUserSessionChanged(); |
| |
| // base::SystemMonitor::DevicesChangedObserver: |
| void OnDevicesChanged(base::SystemMonitor::DeviceType device_type) override; |
| |
| // SystemTrayObserver: |
| void OnSystemTrayBubbleShown() override; |
| void OnFocusLeavingSystemTray(bool reverse) override {} |
| |
| void SetOnCameraListReceivedForTesting(base::OnceClosure callback) { |
| on_camera_list_received_for_test_ = std::move(callback); |
| } |
| |
| base::OneShotTimer* camera_reconnect_timer_for_test() { |
| return &camera_reconnect_timer_; |
| } |
| |
| private: |
| friend class CaptureModeTestApi; |
| |
| // Called to connect to the video capture services's video source provider for |
| // the first time, or when the connection to it is lost. It also queries the |
| // list of currently available cameras by calling the below |
| // GetCameraDevices(). |
| void ReconnectToVideoSourceProvider(); |
| |
| // Retrieves the list of currently available cameras from the video source |
| // provider. |
| void GetCameraDevices(); |
| |
| // Called back asynchronously by the video source provider to give us the list |
| // of currently available camera `devices`. The ID used to make the request to |
| // which this reply belongs is `request_id`. We will ignore any replies for |
| // any older requests than the `most_recent_request_id_`. |
| using RequestId = size_t; |
| void OnCameraDevicesReceived( |
| RequestId request_id, |
| const std::vector<media::VideoCaptureDeviceInfo>& devices); |
| |
| // Shows or hides a preview of the currently selected camera depending on |
| // whether it's currently allowed and whether one is currently selected. |
| void RefreshCameraPreview(); |
| |
| // Triggered when the `camera_reconnect_timer_` fires, indicating that a |
| // previously `selected_camera_` remained disconnected for longer than the |
| // allowed grace period, and therefore it will be cleared. |
| void OnSelectedCameraDisconnected(); |
| |
| // Returns the bounds of the preview widget which doesn't intersect with |
| // system tray, which should be confined within the given `confine_bounds`, |
| // and have the given `preview_size`. Always tries the current |
| // `camera_preview_snap_position_` first. Once a snap position with which the |
| // preview has no collisions is found, it will be set in |
| // `camera_preview_snap_position_`. If the camera preview at all possible snap |
| // positions intersects with system tray, returns the bounds for the current |
| // `camera_preview_snap_position_`. |
| gfx::Rect CalculatePreviewWidgetTargetBounds(const gfx::Rect& confine_bounds, |
| const gfx::Size& preview_size); |
| |
| // Called by `CalculatePreviewWidgetTargetBounds` above. Returns the bounds of |
| // the preview widget that matches the coordinate system of the given |
| // `confine_bounds` with the given `preview_size` at the given |
| // `snap_position`. |
| gfx::Rect GetPreviewWidgetBoundsForSnapPosition( |
| const gfx::Rect& confine_bounds, |
| const gfx::Size& preview_size, |
| CameraPreviewSnapPosition snap_position) const; |
| |
| // Returns the new snap position of the camera preview on drag ended. |
| CameraPreviewSnapPosition CalculateSnapPositionOnDragEnded() const; |
| |
| // Returns the current bounds of camemra preview widget that match the |
| // coordinate system of the confine bounds. |
| gfx::Rect GetCurrentBoundsMatchingConfineBoundsCoordinates() const; |
| |
| // Does post works for camera preview after RefreshCameraPreview(). It |
| // triggers a11y alert based on `was_preview_visible_before` and the current |
| // visibility of `camera_preview_widget_`. `was_preview_visible_before` is the |
| // visibility of the camera preview when RefreshCameraPreview() was called. |
| // It also triggers floating windows bounds update to avoid overlap between |
| // camera preview and floating windows, such as PIP windows and some a11y |
| // panels. |
| void RunPostRefreshCameraPreview(bool was_preview_visible_before); |
| |
| // Sets the given `target_bounds` on the camera preview widget, potentially |
| // animating to it if `animate` is true. Returns true if the bounds actually |
| // changed from the current. |
| bool SetCameraPreviewBounds(const gfx::Rect& target_bounds, bool animate); |
| |
| // Owned by CaptureModeController and guaranteed to be not null and to outlive |
| // `this`. |
| CaptureModeDelegate* const delegate_; |
| |
| // The remote end to the video source provider that exists in the video |
| // capture service. |
| mojo::Remote<video_capture::mojom::VideoSourceProvider> |
| video_source_provider_remote_; |
| |
| CameraInfoList available_cameras_; |
| |
| // The currently selected camera. If its `is_valid()` is false, then no camera |
| // is currently selected. |
| CameraId selected_camera_; |
| |
| base::ObserverList<Observer> observers_; |
| |
| // If bound, will be invoked at the end of the scope of |
| // `OnCameraDevicesReceived()` regardless of whether there was a change in the |
| // available cameras or not, which is different from the behavior of |
| // `Observer::OnAvailableCamerasChanged()` which is called only when there is |
| // a change. |
| base::OnceClosure on_camera_list_received_for_test_; |
| |
| // The camera preview widget and its contents view. |
| views::UniqueWidgetPtr camera_preview_widget_; |
| CameraPreviewView* camera_preview_view_ = nullptr; |
| |
| // A timer used to give a `selected_camera_` that got disconnected a grace |
| // period, so if it reconnects again within this period, its ID is kept around |
| // in `selected_camera_`, otherwise the ID is cleared, effectively resetting |
| // back the camera setting to "Off". |
| base::OneShotTimer camera_reconnect_timer_; |
| |
| // Set to true when a preview of the currently selected camera (if any) should |
| // be shown. This happens when CaptureModeSession is started and switched to |
| // a video recording mode before recording starts. It is reset back to false |
| // when: |
| // - Video recording ends. |
| // - The selected camera is disconnected for longer than a grace period during |
| // recording. |
| // - The capture mode session ends without starting any recording. |
| // - The capture mode session is switched to an image capture mode. |
| bool should_show_preview_ = false; |
| |
| // The ID used for the most recent request made to the video source provider |
| // to get the list of cameras in GetCameraDevices(). More recent requests will |
| // have a larger value IDs than older requests. |
| RequestId most_recent_request_id_ = 0; |
| |
| CameraPreviewSnapPosition camera_preview_snap_position_ = |
| CameraPreviewSnapPosition::kBottomRight; |
| |
| // The location of the previous drag event in screen coordinate. |
| gfx::PointF previous_location_in_screen_; |
| |
| // True when the dragging for `camera_preview_view_` is in progress. |
| bool is_drag_in_progress_ = false; |
| |
| // True if the camera preview is collapsed. Its value will be updated when |
| // the resize button is clicked. The size of the preview widget and the icon |
| // of the resize button will be updated based on it. |
| bool is_camera_preview_collapsed_ = false; |
| |
| // True if it's the first time to update the camera preview's bounds after |
| // it's created. |
| bool is_first_bounds_update_ = false; |
| |
| // True when the device is shutting down, and we should no longer make any |
| // requests to the video capture service. |
| bool is_shutting_down_ = false; |
| |
| // Valid only during recording to track the number of camera disconnections |
| // while recording is in progress. |
| absl::optional<int> in_recording_camera_disconnections_; |
| |
| // Will be set to true the first time the number of connected cameras is |
| // reported. |
| bool did_report_number_of_cameras_before_ = false; |
| |
| // Will be set to true the first user logs in. And we should only request the |
| // camera devices after the first user logs in. |
| bool did_first_user_login_ = false; |
| |
| base::WeakPtrFactory<CaptureModeCameraController> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace ash |
| |
| #endif // ASH_CAPTURE_MODE_CAPTURE_MODE_CAMERA_CONTROLLER_H_ |