// Copyright 2022 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_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_behavior.h"
#include "ash/capture_mode/capture_mode_types.h"
#include "ash/system/tray/system_tray_observer.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.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 CaptureModeBehavior;
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_;
}
bool did_user_ever_change_camera() const {
return did_user_ever_change_camera_;
}
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();
// Reverts the automatic selection of the first available camera if one was
// made by calling the `MaybeSelectFirstCamera()`.
void MaybeRevertAutoCameraSelection();
// 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. `by_user` is true if the selection was made
// explicitly by the user, false otherwise.
void SetSelectedCamera(CameraId camera_id, bool by_user = false);
// 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(const CaptureModeBehavior* active_behavior);
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 OnStatusAreaAnchoredBubbleVisibilityChanged(TrayBubbleView* tray_bubble,
bool visible) 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,
video_capture::mojom::VideoSourceProvider::GetSourceInfosResult,
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`.
const raw_ptr<CaptureModeDelegate> 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_;
raw_ptr<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.
std::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;
// True if the first available camera was auto-selected by calling
// `MaybeSelectFirstCamera()`, false otherwise or if
// `MaybeRevertAutoCameraSelection()` was called to revert back this automatic
// selection.
bool did_make_camera_auto_selection_ = false;
// True if the user ever made an explicit camera selection (i.e. from the
// capture mode settings menu).
bool did_user_ever_change_camera_ = false;
base::WeakPtrFactory<CaptureModeCameraController> weak_ptr_factory_{this};
};
} // namespace ash
#endif // ASH_CAPTURE_MODE_CAPTURE_MODE_CAMERA_CONTROLLER_H_