#include "ash/capture_mode/capture_mode_camera_controller.h"
#include <algorithm>
#include <cstring>
#include <vector>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/capture_mode/capture_mode_camera_preview_view.h"
#include "ash/capture_mode/capture_mode_constants.h"
#include "ash/capture_mode/capture_mode_controller.h"
#include "ash/capture_mode/capture_mode_metrics.h"
#include "ash/capture_mode/capture_mode_session.h"
#include "ash/capture_mode/capture_mode_util.h"
#include "ash/game_dashboard/game_dashboard_controller.h"
#include "ash/public/cpp/capture_mode/capture_mode_delegate.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/tray/system_tray_notifier.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/system/unified/unified_system_tray_controller.h"
#include "ash/wm/pip/pip_controller.h"
#include "ash/wm/pip/pip_positioner.h"
#include "ash/wm/window_state.h"
#include "ash/wm/wm_event.h"
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "media/capture/video/video_capture_device_descriptor.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "ui/aura/window_targeter.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/layer.h"
#include "ui/display/screen.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/views/animation/animation_builder.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/window_properties.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
constexpr base::TimeDelta kDisconnectionGracePeriod = base::Seconds(10);
constexpr base::TimeDelta kCameraBoundsChangeAnimationDuration =
base::Milliseconds(150);
constexpr base::TimeDelta kCameraPreviewFadeOutDuration =
base::Milliseconds(50);
constexpr base::TimeDelta kCameraPreviewFadeInDuration =
base::Milliseconds(150);
using ModelIdToCountMap = std::map<std::string, int>;
int GetNextCameraNumber(const std::string& model_id_or_display_name,
ModelIdToCountMap* cam_models_map) {
return ++(*cam_models_map)[model_id_or_display_name];
}
const std::string& PickModelIdOrDisplayName(
const media::VideoCaptureDeviceDescriptor& descriptor) {
return descriptor.model_id.empty() ? descriptor.display_name()
: descriptor.model_id;
}
bool DidDevicesChange(
const std::vector<media::VideoCaptureDeviceInfo>& incoming_list,
const CameraInfoList& current_list) {
if (incoming_list.size() != current_list.size())
return true;
ModelIdToCountMap cam_models_map;
for (const auto& incoming_camera : incoming_list) {
const auto& device_id = incoming_camera.descriptor.device_id;
const auto iter =
std::ranges::find(current_list, device_id, &CameraInfo::device_id);
if (iter == current_list.end())
return true;
const auto& model_id_or_display_name =
PickModelIdOrDisplayName(incoming_camera.descriptor);
const int cam_number =
GetNextCameraNumber(model_id_or_display_name, &cam_models_map);
const CameraInfo& found_info = *iter;
if (found_info.display_name != incoming_camera.descriptor.display_name() ||
found_info.camera_id.model_id_or_display_name() !=
model_id_or_display_name ||
found_info.camera_id.number() != cam_number) {
return true;
}
}
return false;
}
media::VideoCaptureFormat PickSuitableCaptureFormat(
const gfx::Size& preview_widget_size,
const media::VideoCaptureFormats& supported_formats) {
DCHECK(!supported_formats.empty());
DCHECK_EQ(preview_widget_size.height(), preview_widget_size.width())
<< "The preview widget is always assumed to be a square.";
size_t result_index = 0;
float current_frame_rate = 0.f;
for (size_t i = 0; i < supported_formats.size(); ++i) {
const auto& format = supported_formats[i];
if (format.frame_size.height() > preview_widget_size.height())
break;
if (format.frame_rate >= current_frame_rate && format.frame_rate <= 30.f) {
current_frame_rate = format.frame_rate;
result_index = i;
}
}
return supported_formats[result_index];
}
bool ShouldCameraActLikeAMirror(const CameraInfo& camera_info) {
return camera_info.camera_facing_mode !=
media::MEDIA_VIDEO_FACING_ENVIRONMENT;
}
const CameraInfo* GetCameraInfoById(const CameraId& id,
const CameraInfoList& list) {
const auto iter = std::ranges::find(list, id, &CameraInfo::camera_id);
return iter == list.end() ? nullptr : &(*iter);
}
views::Widget::InitParams CreateWidgetParams(const gfx::Rect& bounds) {
views::Widget::InitParams params(
views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
views::Widget::InitParams::TYPE_POPUP);
params.parent =
CaptureModeController::Get()->GetOnCaptureSurfaceWidgetParentWindow();
params.bounds = bounds;
params.name = "CameraPreviewWidget";
return params;
}
gfx::Rect GetTargetBoundsForBoundsAnimation(
const gfx::Rect& target_bounds,
aura::Window* camera_preview_window) {
gfx::Rect result = target_bounds;
auto* parent = camera_preview_window->parent();
if (parent->GetProperty(wm::kUsesScreenCoordinatesKey))
wm::ConvertRectFromScreen(parent, &result);
return result;
}
gfx::Rect GetCollisionAvoidanceRect(aura::Window* root_window,
aura::Window* preview_parent) {
DCHECK(root_window);
auto* status_area_widget =
RootWindowController::ForWindow(root_window)->GetStatusAreaWidget();
gfx::Rect collision_avoidance_rect;
if (UnifiedSystemTray* unified_system_tray =
status_area_widget->unified_system_tray();
unified_system_tray->IsBubbleShown()) {
collision_avoidance_rect = unified_system_tray->GetBubbleBoundsInScreen();
} else {
const std::vector<raw_ptr<TrayBackgroundView, VectorExperimental>>
tray_buttons = status_area_widget->tray_buttons();
for (ash::TrayBackgroundView* tray_button : tray_buttons) {
if (views::Widget* tray_bubble_widget = tray_button->GetBubbleWidget();
tray_bubble_widget && tray_bubble_widget->IsVisible()) {
collision_avoidance_rect.Union(
tray_bubble_widget->GetWindowBoundsInScreen());
}
}
}
if (auto* game_dashboard_controller = GameDashboardController::Get()) {
if (auto* game_dashboard_context =
game_dashboard_controller->GetGameDashboardContext(
preview_parent)) {
collision_avoidance_rect.Union(
game_dashboard_context->GetToolbarBoundsInScreen());
}
}
return collision_avoidance_rect;
}
void UpdateFloatingPanelBoundsIfNeeded(aura::Window* root_window) {
DCHECK(root_window);
Shell::Get()->accessibility_controller()->UpdateFloatingPanelBoundsIfNeeded();
auto* pip_window_container =
root_window->GetChildById(kShellWindowId_PipContainer);
for (aura::Window* pip_window : pip_window_container->children()) {
auto* pip_window_state = WindowState::Get(pip_window);
if (pip_window_state->IsPip())
Shell::Get()->pip_controller()->UpdatePipBounds();
}
}
gfx::Size CalculatePreviewInitialSize() {
int max_shorter_side = 0;
for (aura::Window* root_window : Shell::GetAllRootWindows()) {
const auto work_area = display::Screen::Get()
->GetDisplayNearestWindow(root_window)
.work_area();
const int shorter_side = std::min(work_area.width(), work_area.height());
max_shorter_side = std::max(max_shorter_side, shorter_side);
}
const int preview_diameter =
max_shorter_side / capture_mode::kCaptureSurfaceShortSideDivider;
return gfx::Size(preview_diameter, preview_diameter);
}
int GetMessageIdForSnapPosition(CameraPreviewSnapPosition snap_position,
bool for_collision_avoidance) {
switch (snap_position) {
case CameraPreviewSnapPosition::kTopRight:
return for_collision_avoidance
? IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_RIGHT_ON_CONFLICT
: IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_RIGHT;
case CameraPreviewSnapPosition::kTopLeft:
return for_collision_avoidance
? IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_LEFT_ON_CONFLICT
: IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_LEFT;
case CameraPreviewSnapPosition::kBottomRight:
return for_collision_avoidance
? IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_RIGHT_ON_CONFLICT
: IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_RIGHT;
case CameraPreviewSnapPosition::kBottomLeft:
return for_collision_avoidance
? IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_LEFT_ON_CONFLICT
: IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_LEFT;
}
}
class CameraPreviewTargeter : public aura::WindowTargeter {
public:
explicit CameraPreviewTargeter(aura::Window* camera_preview_window)
: camera_preview_window_(camera_preview_window) {}
CameraPreviewTargeter(const CameraPreviewTargeter&) = delete;
CameraPreviewTargeter& operator=(const CameraPreviewTargeter&) = delete;
~CameraPreviewTargeter() override = default;
ui::EventTarget* FindTargetForEvent(ui::EventTarget* root,
ui::Event* event) override {
if (event->IsLocatedEvent()) {
auto screen_location = event->AsLocatedEvent()->root_location();
wm::ConvertPointToScreen(camera_preview_window_->GetRootWindow(),
&screen_location);
const gfx::Rect camera_preview_bounds =
camera_preview_window_->GetBoundsInScreen();
const gfx::Point camera_preview_center_point =
camera_preview_bounds.CenterPoint();
const int camera_preview_radius = camera_preview_bounds.width() / 2 -
capture_mode::kCameraPreviewBorderSize;
if ((screen_location - camera_preview_center_point).LengthSquared() >
camera_preview_radius * camera_preview_radius) {
return nullptr;
}
}
return aura::WindowTargeter::FindTargetForEvent(root, event);
}
private:
const raw_ptr<aura::Window> camera_preview_window_;
};
capture_mode_util::AnimationParams BuildCameraVisibilityAnimationParams(
bool target_visibility,
bool apply_scale_up_animation) {
return {target_visibility ? kCameraPreviewFadeInDuration
: kCameraPreviewFadeOutDuration,
gfx::Tween::LINEAR, apply_scale_up_animation};
}
}
CameraId::CameraId(std::string model_id_or_display_name, int number)
: model_id_or_display_name_(std::move(model_id_or_display_name)),
number_(number) {
DCHECK(!model_id_or_display_name_.empty());
DCHECK_GE(number, 1);
}
bool CameraId::operator<(const CameraId& rhs) const {
const int result =
UNSAFE_TODO(std::strcmp(model_id_or_display_name_.c_str(),
rhs.model_id_or_display_name_.c_str()));
return result != 0 ? result : (number_ < rhs.number_);
}
std::string CameraId::ToString() const {
return base::StringPrintf("%s:%0d", model_id_or_display_name_.c_str(),
number_);
}
CameraInfo::CameraInfo(CameraId camera_id,
std::string device_id,
std::string display_name,
const media::VideoCaptureFormats& supported_formats,
media::VideoFacingMode camera_facing_mode)
: camera_id(std::move(camera_id)),
device_id(std::move(device_id)),
display_name(std::move(display_name)),
supported_formats(supported_formats),
camera_facing_mode(camera_facing_mode) {}
CameraInfo::CameraInfo(CameraInfo&&) = default;
CameraInfo& CameraInfo::operator=(CameraInfo&&) = default;
CameraInfo::~CameraInfo() = default;
CaptureModeCameraController::CaptureModeCameraController(
CaptureModeDelegate* delegate)
: delegate_(delegate) {
DCHECK(delegate_);
DCHECK(base::SystemMonitor::Get())
<< "No instance of SystemMonitor exists. If this is a unit test, please "
"create one.";
base::SystemMonitor::Get()->AddDevicesChangedObserver(this);
ReconnectToVideoSourceProvider();
Shell::Get()->system_tray_notifier()->AddSystemTrayObserver(this);
}
CaptureModeCameraController::~CaptureModeCameraController() {
base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this);
Shell::Get()->system_tray_notifier()->RemoveSystemTrayObserver(this);
}
void CaptureModeCameraController::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void CaptureModeCameraController::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void CaptureModeCameraController::MaybeSelectFirstCamera() {
if (!selected_camera_.is_valid() && !available_cameras_.empty()) {
SetSelectedCamera(available_cameras_[0].camera_id);
did_make_camera_auto_selection_ = true;
}
}
void CaptureModeCameraController::MaybeRevertAutoCameraSelection() {
if (did_make_camera_auto_selection_) {
SetSelectedCamera(CameraId());
did_make_camera_auto_selection_ = false;
}
}
bool CaptureModeCameraController::IsCameraDisabledByPolicy() const {
return delegate_->IsCameraDisabledByPolicy();
}
std::string CaptureModeCameraController::GetDisplayNameOfSelectedCamera()
const {
if (selected_camera_.is_valid()) {
const CameraInfo* camera_info =
GetCameraInfoById(selected_camera_, available_cameras_);
if (camera_info)
return camera_info->display_name;
}
return std::string();
}
void CaptureModeCameraController::SetSelectedCamera(CameraId camera_id,
bool by_user) {
if (IsCameraDisabledByPolicy()) {
LOG(WARNING) << "Camera is disabled by policy. Selecting camera: "
<< camera_id.ToString() << " will be ignored.";
camera_id = CameraId{};
}
if (selected_camera_ == camera_id)
return;
did_user_ever_change_camera_ |= by_user;
did_make_camera_auto_selection_ = false;
selected_camera_ = std::move(camera_id);
camera_reconnect_timer_.Stop();
for (auto& observer : observers_)
observer.OnSelectedCameraChanged(selected_camera_);
const std::string camera_display_name = GetDisplayNameOfSelectedCamera();
if (!camera_display_name.empty()) {
capture_mode_util::TriggerAccessibilityAlert(l10n_util::GetStringFUTF8(
IDS_ASH_SCREEN_CAPTURE_SELECTED_CAMERA_CHANGED,
base::UTF8ToUTF16(camera_display_name)));
}
RefreshCameraPreview();
}
void CaptureModeCameraController::SetShouldShowPreview(bool value) {
should_show_preview_ = value;
RefreshCameraPreview();
}
void CaptureModeCameraController::MaybeReparentPreviewWidget() {
if (!camera_preview_widget_)
return;
auto* native_window = camera_preview_widget_->GetNativeWindow();
if (auto* transient_parent = wm::GetTransientParent(native_window))
wm::RemoveTransientChild(transient_parent, native_window);
const bool was_visible_before = camera_preview_widget_->IsVisible();
auto* controller = CaptureModeController::Get();
auto* parent = controller->GetOnCaptureSurfaceWidgetParentWindow();
DCHECK(parent);
if (parent != native_window->parent())
views::Widget::ReparentNativeView(native_window, parent);
DCHECK(!wm::GetTransientParent(native_window));
MaybeUpdatePreviewWidget();
if (was_visible_before != camera_preview_widget_->IsVisible()) {
capture_mode_util::TriggerAccessibilityAlertSoon(
was_visible_before ? IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_HIDDEN
: IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_ON);
}
}
void CaptureModeCameraController::SetCameraPreviewSnapPosition(
CameraPreviewSnapPosition value,
bool animate) {
capture_mode_util::TriggerAccessibilityAlert(
GetMessageIdForSnapPosition(value, false));
camera_preview_snap_position_ = value;
MaybeUpdatePreviewWidget(animate);
}
void CaptureModeCameraController::MaybeUpdatePreviewWidget(bool animate) {
if (!camera_preview_widget_)
return;
auto* controller = CaptureModeController::Get();
DCHECK(controller->IsActive() || controller->is_recording_in_progress());
const gfx::Rect confine_bounds = controller->GetCaptureSurfaceConfineBounds();
const capture_mode_util::CameraPreviewSizeSpecs size_specs =
capture_mode_util::CalculateCameraPreviewSizeSpecs(
confine_bounds.size(), is_camera_preview_collapsed_);
camera_preview_view_->SetIsCollapsible(size_specs.is_collapsible);
const bool should_animate_visibility =
!size_specs.is_surface_too_small && !confine_bounds.IsEmpty() &&
(camera_preview_widget_->GetNativeWindow()->parent()->GetId() !=
kShellWindowId_UnparentedContainer);
const bool did_bounds_change =
size_specs.should_be_visible &&
SetCameraPreviewBounds(
CalculatePreviewWidgetTargetBounds(confine_bounds, size_specs.size),
animate);
const bool did_visibility_change = capture_mode_util::SetWidgetVisibility(
camera_preview_widget_.get(), size_specs.should_be_visible,
!should_animate_visibility
? std::nullopt
: std::make_optional<capture_mode_util::AnimationParams>(
BuildCameraVisibilityAnimationParams(
size_specs.should_be_visible,
is_first_bounds_update_)));
if (controller->IsActive() && !controller->is_recording_in_progress()) {
controller->capture_mode_session()
->OnCameraPreviewBoundsOrVisibilityChanged(
size_specs
.is_surface_too_small,
did_visibility_change ||
did_bounds_change);
}
if (did_bounds_change) {
UpdateFloatingPanelBoundsIfNeeded(
camera_preview_widget_->GetNativeWindow()->GetRootWindow());
}
}
void CaptureModeCameraController::StartDraggingPreview(
const gfx::PointF& screen_location) {
is_drag_in_progress_ = true;
previous_location_in_screen_ = screen_location;
camera_preview_view_->RefreshResizeButtonVisibility();
auto* controller = CaptureModeController::Get();
if (controller->IsActive()) {
controller->capture_mode_session()->OnCameraPreviewDragStarted();
}
Shell::Get()->UpdateCursorCompositingEnabled();
}
void CaptureModeCameraController::ContinueDraggingPreview(
const gfx::PointF& screen_location) {
gfx::Rect current_bounds = GetCurrentBoundsMatchingConfineBoundsCoordinates();
current_bounds.Offset(
gfx::ToRoundedVector2d(screen_location - previous_location_in_screen_));
capture_mode_util::AdjustBoundsWithinConfinedBounds(
CaptureModeController::Get()->GetCaptureSurfaceConfineBounds(),
current_bounds);
camera_preview_widget_->SetBounds(current_bounds);
previous_location_in_screen_ = screen_location;
}
void CaptureModeCameraController::EndDraggingPreview(
const gfx::PointF& screen_location,
bool is_touch) {
ContinueDraggingPreview(screen_location);
SetCameraPreviewSnapPosition(CalculateSnapPositionOnDragEnded(),
true);
is_drag_in_progress_ = false;
camera_preview_view_->RefreshResizeButtonVisibility();
Shell::Get()->UpdateCursorCompositingEnabled();
auto* controller = CaptureModeController::Get();
if (controller->IsActive()) {
controller->capture_mode_session()->OnCameraPreviewDragEnded(
gfx::ToRoundedPoint(screen_location), is_touch);
}
}
void CaptureModeCameraController::ToggleCameraPreviewSize() {
DCHECK(camera_preview_view_);
is_camera_preview_collapsed_ = !is_camera_preview_collapsed_;
MaybeUpdatePreviewWidget(true);
}
void CaptureModeCameraController::OnCaptureSessionStarted() {
GetCameraDevices();
}
void CaptureModeCameraController::OnRecordingStarted(
const CaptureModeBehavior* active_behavior) {
if (selected_camera_.is_valid() && !camera_preview_widget_)
SetShouldShowPreview(false);
in_recording_camera_disconnections_ = 0;
const bool starts_with_camera = camera_preview_widget();
RecordRecordingStartsWithCamera(starts_with_camera, active_behavior);
RecordCameraSizeOnStart(is_camera_preview_collapsed_
? CaptureModeCameraSize::kCollapsed
: CaptureModeCameraSize::kExpanded);
RecordCameraPositionOnStart(camera_preview_snap_position_);
}
void CaptureModeCameraController::OnRecordingEnded() {
DCHECK(in_recording_camera_disconnections_);
SetShouldShowPreview(false);
RecordCameraDisconnectionsDuringRecordings(
*in_recording_camera_disconnections_);
in_recording_camera_disconnections_.reset();
}
void CaptureModeCameraController::OnFrameHandlerFatalError() {
if (!camera_preview_widget_)
return;
DCHECK(camera_preview_view_);
DCHECK_EQ(selected_camera_, camera_preview_view_->camera_id());
std::erase_if(available_cameras_, [&](const CameraInfo& info) {
return selected_camera_ == info.camera_id;
});
for (auto& observer : observers_)
observer.OnAvailableCamerasChanged(available_cameras_);
RefreshCameraPreview();
GetCameraDevices();
}
void CaptureModeCameraController::OnShuttingDown() {
SetShouldShowPreview(false);
DCHECK(!should_show_preview_);
DCHECK(!camera_preview_widget_);
is_shutting_down_ = true;
}
void CaptureModeCameraController::PseudoFocusCameraPreview() {
DCHECK(camera_preview_view_);
DCHECK(camera_preview_view_->GetVisible());
auto* controller = CaptureModeController::Get();
DCHECK(!controller->IsActive());
DCHECK(controller->is_recording_in_progress());
camera_preview_view_->PseudoFocus();
camera_preview_view_->UpdateA11yOverrideWindow();
}
void CaptureModeCameraController::OnActiveUserSessionChanged() {
if (!did_first_user_login_) {
did_first_user_login_ = true;
GetCameraDevices();
}
}
void CaptureModeCameraController::OnDevicesChanged(
base::SystemMonitor::DeviceType device_type) {
if (device_type == base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE)
GetCameraDevices();
}
void CaptureModeCameraController::OnSystemTrayBubbleShown() {
MaybeUpdatePreviewWidget(true);
}
void CaptureModeCameraController::OnStatusAreaAnchoredBubbleVisibilityChanged(
TrayBubbleView* tray_bubble,
bool visible) {
if (visible) {
MaybeUpdatePreviewWidget(true);
}
}
void CaptureModeCameraController::ReconnectToVideoSourceProvider() {
if (is_shutting_down_)
return;
video_source_provider_remote_.reset();
most_recent_request_id_ = 0;
delegate_->ConnectToVideoSourceProvider(
video_source_provider_remote_.BindNewPipeAndPassReceiver());
video_source_provider_remote_.set_disconnect_handler(base::BindOnce(
&CaptureModeCameraController::ReconnectToVideoSourceProvider,
base::Unretained(this)));
GetCameraDevices();
}
void CaptureModeCameraController::GetCameraDevices() {
if (is_shutting_down_ || !did_first_user_login_)
return;
DCHECK(video_source_provider_remote_);
video_source_provider_remote_->GetSourceInfos(base::BindOnce(
&CaptureModeCameraController::OnCameraDevicesReceived,
weak_ptr_factory_.GetWeakPtr(), ++most_recent_request_id_));
}
void CaptureModeCameraController::OnCameraDevicesReceived(
RequestId request_id,
video_capture::mojom::VideoSourceProvider::GetSourceInfosResult,
const std::vector<media::VideoCaptureDeviceInfo>& devices) {
if (request_id < most_recent_request_id_) {
return;
}
DCHECK_EQ(request_id, most_recent_request_id_);
base::ScopedClosureRunner deferred_runner;
if (on_camera_list_received_for_test_) {
deferred_runner.ReplaceClosure(
std::move(on_camera_list_received_for_test_));
}
const bool should_report_cameras_number =
!did_report_number_of_cameras_before_ ||
(devices.size() != available_cameras_.size());
if (should_report_cameras_number) {
did_report_number_of_cameras_before_ = true;
RecordNumberOfConnectedCameras(devices.size());
}
if (!DidDevicesChange(devices, available_cameras_))
return;
available_cameras_.clear();
ModelIdToCountMap cam_models_map;
for (const auto& device : devices) {
const auto& descriptor = device.descriptor;
const auto& model_id_or_display_name = PickModelIdOrDisplayName(descriptor);
const int cam_number =
GetNextCameraNumber(model_id_or_display_name, &cam_models_map);
available_cameras_.emplace_back(
CameraId(model_id_or_display_name, cam_number), descriptor.device_id,
descriptor.display_name(), device.supported_formats, descriptor.facing);
}
for (auto& observer : observers_)
observer.OnAvailableCamerasChanged(available_cameras_);
RefreshCameraPreview();
}
void CaptureModeCameraController::RefreshCameraPreview() {
if (is_shutting_down_)
return;
bool was_visible_before = false;
aura::Window* old_root = nullptr;
if (camera_preview_widget_) {
was_visible_before = camera_preview_widget_->IsVisible();
old_root = camera_preview_widget_->GetNativeWindow()->GetRootWindow();
}
absl::Cleanup deferred_runner = [this, was_visible_before] {
RunPostRefreshCameraPreview(was_visible_before);
};
const CameraInfo* camera_info = nullptr;
if (selected_camera_.is_valid()) {
if (camera_info = GetCameraInfoById(selected_camera_, available_cameras_);
camera_info) {
if (camera_reconnect_timer_.IsRunning()) {
const base::TimeDelta remaining_time =
camera_reconnect_timer_.desired_run_time() - base::TimeTicks::Now();
const int reconnect_duration_in_seconds =
(kDisconnectionGracePeriod - remaining_time).InSeconds();
RecordCameraReconnectDuration(reconnect_duration_in_seconds,
kDisconnectionGracePeriod.InSeconds());
capture_mode_util::TriggerAccessibilityAlert(
IDS_ASH_SCREEN_CAPTURE_CAMERA_RECONNECTED);
}
camera_reconnect_timer_.Stop();
if (!should_show_preview_)
camera_info = nullptr;
} else {
camera_reconnect_timer_.Start(
FROM_HERE, kDisconnectionGracePeriod, this,
&CaptureModeCameraController::OnSelectedCameraDisconnected);
if (in_recording_camera_disconnections_)
++(*in_recording_camera_disconnections_);
capture_mode_util::TriggerAccessibilityAlert(
IDS_ASH_SCREEN_CAPTURE_CAMERA_DISCONNECTED);
}
}
if (!camera_info || camera_info->supported_formats.empty()) {
camera_preview_widget_.reset();
camera_preview_view_ = nullptr;
if (old_root)
UpdateFloatingPanelBoundsIfNeeded(old_root);
return;
}
if (camera_preview_view_ &&
camera_preview_view_->camera_id() != selected_camera_) {
camera_preview_widget_.reset();
camera_preview_view_ = nullptr;
}
DCHECK(!IsCameraDisabledByPolicy());
if (!camera_preview_widget_) {
const auto initial_temp_bounds = gfx::Rect(CalculatePreviewInitialSize());
camera_preview_widget_ = std::make_unique<views::Widget>();
camera_preview_widget_->Init(CreateWidgetParams(initial_temp_bounds));
auto* camera_preview_window = camera_preview_widget_->GetNativeWindow();
camera_preview_window->SetEventTargeter(
std::make_unique<CameraPreviewTargeter>(camera_preview_window));
mojo::Remote<video_capture::mojom::VideoSource> camera_video_source;
video_source_provider_remote_->GetVideoSource(
camera_info->device_id,
camera_video_source.BindNewPipeAndPassReceiver());
camera_preview_view_ = camera_preview_widget_->SetContentsView(
std::make_unique<CameraPreviewView>(
this, selected_camera_, std::move(camera_video_source),
PickSuitableCaptureFormat(initial_temp_bounds.size(),
camera_info->supported_formats),
ShouldCameraActLikeAMirror(*camera_info)));
camera_preview_view_->Initialize();
ui::Layer* layer = camera_preview_widget_->GetLayer();
layer->SetFillsBoundsOpaquely(false);
layer->SetMasksToBounds(true);
is_first_bounds_update_ = true;
MaybeReparentPreviewWidget();
is_first_bounds_update_ = false;
}
DCHECK(camera_preview_view_);
DCHECK_EQ(selected_camera_, camera_preview_view_->camera_id());
}
void CaptureModeCameraController::OnSelectedCameraDisconnected() {
DCHECK(selected_camera_.is_valid());
LOG(WARNING)
<< "Selected camera: " << selected_camera_.ToString()
<< " remained disconnected for longer than the grace period. Clearing.";
SetSelectedCamera(CameraId());
}
gfx::Rect CaptureModeCameraController::CalculatePreviewWidgetTargetBounds(
const gfx::Rect& confine_bounds,
const gfx::Size& preview_size) {
auto* controller = CaptureModeController::Get();
aura::Window* parent =
camera_preview_widget_
? camera_preview_widget_->GetNativeWindow()->parent()
: controller->GetOnCaptureSurfaceWidgetParentWindow();
DCHECK(parent);
const gfx::Rect collision_rect_screen =
GetCollisionAvoidanceRect(parent->GetRootWindow(), parent);
std::vector<CameraPreviewSnapPosition> snap_positions = {
CameraPreviewSnapPosition::kBottomRight,
CameraPreviewSnapPosition::kTopRight, CameraPreviewSnapPosition::kTopLeft,
CameraPreviewSnapPosition::kBottomLeft};
std::erase(snap_positions, camera_preview_snap_position_);
snap_positions.insert(snap_positions.begin(), camera_preview_snap_position_);
gfx::Rect current_preview_bounds;
for (CameraPreviewSnapPosition snap_position : snap_positions) {
gfx::Rect preview_bounds = GetPreviewWidgetBoundsForSnapPosition(
confine_bounds, preview_size, snap_position);
if (!current_preview_bounds.IsEmpty())
current_preview_bounds = preview_bounds;
gfx::Rect preview_bounds_in_screen = preview_bounds;
if (!parent->GetProperty(wm::kUsesScreenCoordinatesKey))
wm::ConvertRectToScreen(parent, &preview_bounds_in_screen);
if (!preview_bounds_in_screen.Intersects(collision_rect_screen)) {
if (snap_position != camera_preview_snap_position_) {
camera_preview_snap_position_ = snap_position;
capture_mode_util::TriggerAccessibilityAlert(
GetMessageIdForSnapPosition(snap_position,
true));
}
return preview_bounds;
}
}
return current_preview_bounds;
}
gfx::Rect CaptureModeCameraController::GetPreviewWidgetBoundsForSnapPosition(
const gfx::Rect& confine_bounds,
const gfx::Size& preview_size,
CameraPreviewSnapPosition snap_position) const {
auto* controller = CaptureModeController::Get();
DCHECK(controller->IsActive() || controller->is_recording_in_progress());
if (confine_bounds.IsEmpty())
return gfx::Rect(preview_size);
gfx::Point origin;
switch (snap_position) {
case CameraPreviewSnapPosition::kTopLeft:
origin = confine_bounds.origin();
origin.Offset(capture_mode::kSpaceBetweenCameraPreviewAndEdges,
capture_mode::kSpaceBetweenCameraPreviewAndEdges);
break;
case CameraPreviewSnapPosition::kBottomLeft:
origin = gfx::Point(
confine_bounds.x() + capture_mode::kSpaceBetweenCameraPreviewAndEdges,
confine_bounds.bottom() - preview_size.height() -
capture_mode::kSpaceBetweenCameraPreviewAndEdges);
break;
case CameraPreviewSnapPosition::kBottomRight:
origin = gfx::Point(confine_bounds.right() - preview_size.width() -
capture_mode::kSpaceBetweenCameraPreviewAndEdges,
confine_bounds.bottom() - preview_size.height() -
capture_mode::kSpaceBetweenCameraPreviewAndEdges);
break;
case CameraPreviewSnapPosition::kTopRight:
origin = gfx::Point(confine_bounds.right() - preview_size.width() -
capture_mode::kSpaceBetweenCameraPreviewAndEdges,
confine_bounds.y() +
capture_mode::kSpaceBetweenCameraPreviewAndEdges);
break;
}
return gfx::Rect(origin, preview_size);
}
CameraPreviewSnapPosition
CaptureModeCameraController::CalculateSnapPositionOnDragEnded() const {
const gfx::Point center_point_of_preview_widget =
GetCurrentBoundsMatchingConfineBoundsCoordinates().CenterPoint();
const gfx::Point center_point_of_confine_bounds =
CaptureModeController::Get()
->GetCaptureSurfaceConfineBounds()
.CenterPoint();
if (center_point_of_preview_widget.x() < center_point_of_confine_bounds.x()) {
return center_point_of_preview_widget.y() <
center_point_of_confine_bounds.y()
? CameraPreviewSnapPosition::kTopLeft
: CameraPreviewSnapPosition::kBottomLeft;
}
return center_point_of_preview_widget.y() < center_point_of_confine_bounds.y()
? CameraPreviewSnapPosition::kTopRight
: CameraPreviewSnapPosition::kBottomRight;
}
gfx::Rect
CaptureModeCameraController::GetCurrentBoundsMatchingConfineBoundsCoordinates()
const {
aura::Window* preview_window = camera_preview_widget_->GetNativeWindow();
aura::Window* parent = preview_window->parent();
if (parent->GetProperty(wm::kUsesScreenCoordinatesKey))
return preview_window->GetBoundsInScreen();
return preview_window->bounds();
}
void CaptureModeCameraController::RunPostRefreshCameraPreview(
bool was_preview_visible_before) {
const bool is_preview_visible_now =
camera_preview_widget_ && camera_preview_widget_->IsVisible();
if (was_preview_visible_before != is_preview_visible_now) {
capture_mode_util::TriggerAccessibilityAlertSoon(
was_preview_visible_before
? IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_HIDDEN
: IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_ON);
}
if (camera_preview_widget_) {
UpdateFloatingPanelBoundsIfNeeded(
camera_preview_widget_->GetNativeWindow()->GetRootWindow());
}
}
bool CaptureModeCameraController::SetCameraPreviewBounds(
const gfx::Rect& target_bounds,
bool animate) {
DCHECK(camera_preview_widget_);
const auto current_bounds =
GetCurrentBoundsMatchingConfineBoundsCoordinates();
if (target_bounds == current_bounds)
return false;
auto* preview_window = camera_preview_widget_->GetNativeWindow();
if (animate) {
views::AnimationBuilder builder;
builder.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
if (target_bounds.size() == current_bounds.size()) {
camera_preview_widget_->SetBounds(target_bounds);
gfx::Transform transform;
transform.Translate(current_bounds.CenterPoint() -
target_bounds.CenterPoint());
ui::Layer* layer = preview_window->layer();
layer->SetTransform(transform);
builder.Once()
.SetDuration(kCameraBoundsChangeAnimationDuration)
.SetTransform(layer, gfx::Transform(),
gfx::Tween::ACCEL_5_70_DECEL_90);
} else {
const auto target_bounds_in_parent =
GetTargetBoundsForBoundsAnimation(target_bounds, preview_window);
builder.Once()
.SetDuration(kCameraBoundsChangeAnimationDuration)
.SetBounds(preview_window, target_bounds_in_parent,
gfx::Tween::ACCEL_20_DECEL_100);
}
} else {
camera_preview_widget_->SetBounds(target_bounds);
}
return true;
}
}