123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104 |
- // 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.
- #include "ash/capture_mode/capture_mode_camera_controller.h"
- #include <algorithm>
- #include <cstring>
- #include "ash/accessibility/accessibility_controller_impl.h"
- #include "ash/capture_mode/capture_mode_button.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/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/message_center/unified_message_center_bubble.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_positioner.h"
- #include "ash/wm/window_state.h"
- #include "ash/wm/wm_event.h"
- #include "base/bind.h"
- #include "base/callback_helpers.h"
- #include "base/check.h"
- #include "base/containers/contains.h"
- #include "base/strings/stringprintf.h"
- #include "base/time/time.h"
- #include "media/capture/video/video_capture_device_descriptor.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 {
- // The maximum amount of time we allow a `selected_camera_` to remain
- // disconnected before we consider it gone forever, and we clear its ID from
- // `selected_camera_`.
- constexpr base::TimeDelta kDisconnectionGracePeriod = base::Seconds(10);
- // The animation duration for the bounds change operation on the camera preview.
- constexpr base::TimeDelta kCameraBoundsChangeAnimationDuration =
- base::Milliseconds(150);
- // The duration for the camera preview fading out process.
- constexpr base::TimeDelta kCameraPreviewFadeOutDuration =
- base::Milliseconds(50);
- // The duration for the camera preview fading in process.
- constexpr base::TimeDelta kCameraPreviewFadeInDuration =
- base::Milliseconds(150);
- // Defines a map type to map a camera model ID (or display name) to the number
- // of cameras of that model that are currently connected.
- using ModelIdToCountMap = std::map<std::string, int>;
- // Using the given `cam_models_map` which tracks the number of cameras connected
- // of each model, returns the next `CameraId::number` for the given
- // `model_id_or_display_name`.
- int GetNextCameraNumber(const std::string& model_id_or_display_name,
- ModelIdToCountMap* cam_models_map) {
- return ++(*cam_models_map)[model_id_or_display_name];
- }
- // Returns a reference to either the model ID (if available) or the display name
- // from the given `descriptor`.
- const std::string& PickModelIdOrDisplayName(
- const media::VideoCaptureDeviceDescriptor& descriptor) {
- return descriptor.model_id.empty() ? descriptor.display_name()
- : descriptor.model_id;
- }
- // Returns true if the `incoming_list` (supplied by the video source provider)
- // contains different items than the ones in `current_list` (which is the
- // currently `available_cameras_` maintained by `CaptureModeCameraController`).
- 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::find_if(current_list.begin(), current_list.end(),
- [device_id](const CameraInfo& info) {
- return info.device_id == 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) {
- // It is unexpected that the supported formats of the same camera device
- // change, so we ignore comparing them here.
- return true;
- }
- }
- return false;
- }
- // Picks and returns the most suitable supported camera format from the given
- // list of `supported_formats` for the given camera `preview_widget_size`.
- // Note that this assumes that `supported_formats` is sorted as described in the
- // documentation of `CameraInfo::supported_formats`.
- 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];
- // Once we find a format with a larger height than the preview's, we stop
- // and return what we found so far.
- 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];
- }
- // Only rear device cameras marked as facing the environment should not act as
- // a mirror (those cameras are typically not used as selfie cams).
- bool ShouldCameraActLikeAMirror(const CameraInfo& camera_info) {
- return camera_info.camera_facing_mode !=
- media::MEDIA_VIDEO_FACING_ENVIRONMENT;
- }
- // Returns the CameraInfo item in `list` whose ID is equal to the given `id`, or
- // nullptr if no such item exists.
- const CameraInfo* GetCameraInfoById(const CameraId& id,
- const CameraInfoList& list) {
- const auto iter = std::find_if(
- list.begin(), list.end(),
- [&id](const CameraInfo& info) { return info.camera_id == id; });
- return iter == list.end() ? nullptr : &(*iter);
- }
- // Returns the widget init params needed to create the camera preview widget.
- views::Widget::InitParams CreateWidgetParams(const gfx::Rect& bounds) {
- views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
- params.parent = CaptureModeController::Get()->GetCameraPreviewParentWindow();
- params.bounds = bounds;
- params.name = "CameraPreviewWidget";
- return params;
- }
- // Called by `ContinueDraggingPreview` to make sure camera preview is not
- // dragged outside of the capture surface.
- void AdjustBoundsWithinConfinedBounds(const gfx::Rect& confined_bounds,
- gfx::Rect& preview_bounds) {
- const int x = preview_bounds.x();
- if (int offset = x - confined_bounds.x(); offset < 0) {
- preview_bounds.set_x(x - offset);
- } else if (int offset = confined_bounds.right() - preview_bounds.right();
- offset < 0) {
- preview_bounds.set_x(x + offset);
- }
- const int y = preview_bounds.y();
- if (int offset = y - confined_bounds.y(); offset < 0) {
- preview_bounds.set_y(y - offset);
- } else if (int offset = confined_bounds.bottom() - preview_bounds.bottom();
- offset < 0) {
- preview_bounds.set_y(y + offset);
- }
- }
- // Returns the bounds that should be used in the bounds animation of the given
- // `camera_preview_window`. If this window is parented to a window that uses
- // screen coordinates, then the given `target_bounds` are in screen coordinates,
- // and cannot be used for bounds animation (bounds animates relative to the
- // window's parent). In this case, the bounds returned are in parent
- // coordinates.
- 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) {
- DCHECK(root_window);
- UnifiedSystemTray* tray = RootWindowController::ForWindow(root_window)
- ->GetStatusAreaWidget()
- ->unified_system_tray();
- if (!tray->IsBubbleShown())
- return gfx::Rect();
- gfx::Rect collision_avoidance_rect = tray->GetBubbleBoundsInScreen();
- auto* message_center_bubble = tray->message_center_bubble();
- if (message_center_bubble->IsMessageCenterVisible())
- collision_avoidance_rect.Union(message_center_bubble->GetBoundsInScreen());
- // TODO(conniekxu): Return a vector of collision avoidance rects including
- // other system UIs, like launcher.
- return collision_avoidance_rect;
- }
- // Called to avoid overlap between camera preview and system UIs when camera
- // preview's bounds are changed, camera preview is created or destroyed.
- 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())
- pip_window_state->UpdatePipBounds();
- }
- }
- // Calculates a temporary suitable size to use for creating the preview widget.
- // This size will be used to pick a suitable camera capture format, hence it
- // will be based on the maximum size the preview can ever get to on the device
- // with the current configuration.
- gfx::Size CalculatePreviewInitialSize() {
- int max_shorter_side = 0;
- for (auto* root_window : Shell::GetAllRootWindows()) {
- const auto work_area = display::Screen::GetScreen()
- ->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);
- }
- // Returns the appropriate `message_id` for ChromeVox alert on setting camera
- // preview snap position. `for_collision_avoidance` indicates whether the
- // preview snap position updating happens because of its bounds overlap with
- // other system surfaces.
- 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;
- }
- }
- // Defines a window targeter that will be installed on the camera preview
- // widget's window so that we can allow located events outside of the camera
- // preview circle to go through and not be consumed by the camera preview. This
- // enables the user to interact with other UI components below camera preview.
- 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;
- // aura::WindowTargeter:
- 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;
- // Check if events are outside of the camera preview circle by comparing
- // if the distance between screen location and center of camera preview is
- // larger than camera preview circle's radius. If it's larger, allow the
- // events to go through so that they can be used by other UI components
- // below camera preview.
- if ((screen_location - camera_preview_center_point).LengthSquared() >
- camera_preview_radius * camera_preview_radius) {
- return nullptr;
- }
- }
- return aura::WindowTargeter::FindTargetForEvent(root, event);
- }
- private:
- aura::Window* const 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};
- }
- } // namespace
- // -----------------------------------------------------------------------------
- // CameraId:
- 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 = 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::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::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);
- }
- 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_);
- // `camera_info` might not exist in the test even though the
- // `selected_camera_` is valid.
- if (camera_info)
- return camera_info->display_name;
- }
- return std::string();
- }
- void CaptureModeCameraController::SetSelectedCamera(CameraId camera_id) {
- // When cameras are disabled by policy, we don't allow any camera selection.
- 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;
- 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;
- // Remove the camera preview from its transient parent if it has one. And
- // reparenting it to the corresponding parent again. This is done to keep
- // the camera preview in a correct state that is not a transient child of
- // its parent.
- 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->GetCameraPreviewParentWindow();
- 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) {
- // Trigger a11y alert on setting camera preview snap position even though the
- // snap position may actually not change.
- capture_mode_util::TriggerAccessibilityAlert(
- GetMessageIdForSnapPosition(value, /*for_collision_avoidance=*/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->GetCameraPreviewConfineBounds();
- 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);
- // If the surface within which the preview is confined becomes too small, the
- // preview should hide immediately to avoid seeing it hide with animation
- // outside the bounds of the new confine bounds. https://crbug.com/1320087.
- const bool should_animate_visibility =
- !size_specs.is_surface_too_small && !confine_bounds.IsEmpty() &&
- (camera_preview_widget_->GetNativeWindow()->parent()->GetId() !=
- kShellWindowId_UnparentedContainer);
- // We don't need to update the bounds if the widget is hidden.
- // Also notice we should update the bounds before updating the visibility
- // since when `is_first_bounds_update_` is true, we should apply the scale up
- // animation which requires the bounds are set first to the camera preview.
- 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
- ? absl::nullopt
- : absl::make_optional<capture_mode_util::AnimationParams>(
- BuildCameraVisibilityAnimationParams(
- /*target_visibility=*/size_specs.should_be_visible,
- /*apply_scale_up_animation=*/is_first_bounds_update_)));
- if (controller->IsActive() && !controller->is_recording_in_progress()) {
- controller->capture_mode_session()
- ->OnCameraPreviewBoundsOrVisibilityChanged(
- /*capture_surface_became_too_small=*/size_specs
- .is_surface_too_small,
- /*did_bounds_or_visibility_change=*/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();
- // Use cursor compositing instead of the platform cursor when dragging to
- // ensure the cursor is aligned with the camera preview.
- 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_));
- AdjustBoundsWithinConfinedBounds(
- CaptureModeController::Get()->GetCameraPreviewConfineBounds(),
- 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(),
- /*animate=*/true);
- is_drag_in_progress_ = false;
- camera_preview_view_->RefreshResizeButtonVisibility();
- // Disable cursor compositing at the end of the drag.
- 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(/*animate=*/true);
- }
- void CaptureModeCameraController::OnCaptureSessionStarted() {
- GetCameraDevices();
- }
- void CaptureModeCameraController::OnRecordingStarted(
- bool is_in_projector_mode) {
- // Check if there's a camera disconnection that happened before recording
- // starts. In this case, we don't want the camera preview to show, even if the
- // camera reconnects within the allowed grace period.
- 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, is_in_projector_mode);
- 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());
- base::EraseIf(available_cameras_, [&](const CameraInfo& info) {
- return selected_camera_ == info.camera_id;
- });
- for (auto& observer : observers_)
- observer.OnAvailableCamerasChanged(available_cameras_);
- RefreshCameraPreview();
- GetCameraDevices();
- }
- void CaptureModeCameraController::OnShuttingDown() {
- // This should destroy any camera preview if present.
- 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(/*animate=*/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,
- const std::vector<media::VideoCaptureDeviceInfo>& devices) {
- if (request_id < most_recent_request_id_) {
- // Ignore any out-dated requests replies, since a reply from a more recent
- // request is pending.
- return;
- }
- DCHECK_EQ(request_id, most_recent_request_id_);
- // Run the optional for-test closure at the exit of this function's scope.
- 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();
- }
- // Trigger a11y alert and update floating windows bounds when camera preview
- // is created or destroyed. The reason to trigger
- // `RunPostRefreshCameraPreview` at the exit of this function is we should
- // wait for camera preview's creation or destruction to be finished.
- base::ScopedClosureRunner deferred_runner(
- base::BindOnce(&CaptureModeCameraController::RunPostRefreshCameraPreview,
- weak_ptr_factory_.GetWeakPtr(), 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);
- }
- // When a selected camera becomes available, we stop any grace period
- // timer (if any), and decide whether to show or hide the preview widget
- // based on the current value of `should_show_preview_`.
- camera_reconnect_timer_.Stop();
- if (!should_show_preview_)
- camera_info = nullptr;
- } else {
- // Here the selected camera is disconnected, we'll give it a grace period
- // just in case it may reconnect again (this helps in the case of flaky
- // camera connections).
- 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_preview_widget_.reset();
- camera_preview_view_ = nullptr;
- if (old_root)
- UpdateFloatingPanelBoundsIfNeeded(old_root);
- return;
- }
- // Destroying the existing camera preview widget before recreating a new one
- // when a different camera was selected.
- 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)));
- ui::Layer* layer = camera_preview_widget_->GetLayer();
- layer->SetFillsBoundsOpaquely(false);
- layer->SetMasksToBounds(true);
- // Set `is_first_bounds_update_` to true here right after it's recreated.
- // And set it back to false after camera preview is parented and updated to
- // the correct bounds and with correct visibility. The value of
- // `is_first_bounds_update_` will be used in `FadeInCameraPreview`, if it's
- // true, scale up animation will be applied to show camera preview.
- 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->GetCameraPreviewParentWindow();
- DCHECK(parent);
- const gfx::Rect collision_rect_screen =
- GetCollisionAvoidanceRect(parent->GetRootWindow());
- std::vector<CameraPreviewSnapPosition> snap_positions = {
- CameraPreviewSnapPosition::kBottomRight,
- CameraPreviewSnapPosition::kTopRight, CameraPreviewSnapPosition::kTopLeft,
- CameraPreviewSnapPosition::kBottomLeft};
- // Move `camera_preview_snap_position_` to the beginning of `snap_positions`
- // vector, since we should always try the current snap position first.
- base::EraseIf(snap_positions,
- [this](CameraPreviewSnapPosition snap_position) {
- return snap_position == camera_preview_snap_position_;
- });
- snap_positions.insert(snap_positions.begin(), camera_preview_snap_position_);
- // Cache the current preview bounds and return it directly when we find no
- // other snap position with no collisions.
- 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;
- // Need to convert preview bounds to screen bounds if it's not since we
- // need to check whether it intersects with system tray bounds in screen.
- // Make sure we use the same coordinate system before we make the
- // comparison.
- 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,
- /*for_collision_avoidance=*/true));
- }
- // Notice return `preview_bounds` instead of `preview_bounds_in_screen`,
- // since it's the target bounds for camera preview in its parent's
- // coordinate system.
- 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()
- ->GetCameraPreviewConfineBounds()
- .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 the size changes, that means the preview view has to re-layout to keep
- // the resize button at the same size, and at the same offset from the
- // bottom of the preview. In this case we have to use bounds animation to
- // achieve this. Otherwise, transform animation will cause the button to
- // shrink or expand during the animation.
- if (target_bounds.size() == current_bounds.size()) {
- // Use transform animation.
- 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 {
- // Use bounds animation.
- 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;
- }
- } // namespace ash
|