blob: e9f0503595dcb58e863b847f91219249a13b257e [file] [log] [blame]
// Copyright 2021 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_settings_view.h"
#include <memory>
#include <string>
#include "ash/capture_mode/capture_mode_bar_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_toggle_button.h"
#include "ash/constants/ash_features.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/background.h"
#include "ui/views/controls/separator.h"
#include "ui/views/layout/box_layout.h"
namespace ash {
namespace {
constexpr gfx::Size kSettingsSize{256, 248};
constexpr gfx::RoundedCornersF kBorderRadius{10.f};
// Returns the bounds of the settings widget in screen coordinates relative to
// the bounds of the |capture_mode_bar_view| based on its given preferred
// |settings_view_size|.
gfx::Rect GetWidgetBounds(CaptureModeBarView* capture_mode_bar_view,
const gfx::Size& settings_view_size) {
DCHECK(capture_mode_bar_view);
return gfx::Rect(
capture_mode_bar_view->settings_button()->GetBoundsInScreen().right() -
kSettingsSize.width(),
capture_mode_bar_view->GetBoundsInScreen().y() -
capture_mode::kSpaceBetweenCaptureBarAndSettingsMenu -
settings_view_size.height(),
kSettingsSize.width(), settings_view_size.height());
}
CaptureModeController::CaptureFolder GetCurrentCaptureFolder() {
return CaptureModeController::Get()->GetCurrentCaptureFolder();
}
} // namespace
CaptureModeSettingsView::CaptureModeSettingsView(CaptureModeSession* session,
bool is_in_projector_mode)
: capture_mode_session_(session) {
auto* controller = CaptureModeController::Get();
if (!controller->is_recording_in_progress()) {
audio_input_menu_group_ =
AddChildView(std::make_unique<CaptureModeMenuGroup>(
this, kCaptureModeMicIcon,
l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_AUDIO_INPUT)));
if (!is_in_projector_mode) {
audio_input_menu_group_->AddOption(
l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_AUDIO_INPUT_OFF),
kAudioOff);
}
audio_input_menu_group_->AddOption(
l10n_util::GetStringUTF16(
IDS_ASH_SCREEN_CAPTURE_AUDIO_INPUT_MICROPHONE),
kAudioMicrophone);
}
if (features::IsCaptureModeSelfieCameraEnabled() &&
!controller->is_recording_in_progress()) {
separator_1_ = AddChildView(std::make_unique<views::Separator>());
separator_1_->SetColorId(ui::kColorAshSystemUIMenuSeparator);
auto* camera_controller = controller->camera_controller();
const bool managed_by_policy =
camera_controller->IsCameraDisabledByPolicy();
// Even if the camera feature is managed by policy, we still want to observe
// the camera controller, since we need to be notified with camera additions
// and removals, which affect the visibility of the `camera_menu_group_`.
camera_controller->AddObserver(this);
camera_menu_group_ = AddChildView(std::make_unique<CaptureModeMenuGroup>(
this, kCaptureModeCameraIcon,
l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_CAMERA),
managed_by_policy));
AddCameraOptions(camera_controller->available_cameras(), managed_by_policy);
}
if (!is_in_projector_mode) {
separator_2_ = AddChildView(std::make_unique<views::Separator>());
separator_2_->SetColorId(ui::kColorAshSystemUIMenuSeparator);
save_to_menu_group_ = AddChildView(std::make_unique<CaptureModeMenuGroup>(
this, kCaptureModeFolderIcon,
l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_SAVE_TO)));
save_to_menu_group_->AddOption(
l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_SAVE_TO_DOWNLOADS),
kDownloadsFolder);
save_to_menu_group_->AddMenuItem(
base::BindRepeating(
&CaptureModeSettingsView::OnSelectFolderMenuItemPressed,
base::Unretained(this)),
l10n_util::GetStringUTF16(
IDS_ASH_SCREEN_CAPTURE_SAVE_TO_SELECT_FOLDER));
}
SetPaintToLayer();
SetBackground(
views::CreateSolidBackground(AshColorProvider::Get()->GetBaseLayerColor(
AshColorProvider::BaseLayerType::kTransparent80)));
layer()->SetFillsBoundsOpaquely(false);
layer()->SetRoundedCornerRadius(kBorderRadius);
layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
}
CaptureModeSettingsView::~CaptureModeSettingsView() {
if (features::IsCaptureModeSelfieCameraEnabled())
CaptureModeController::Get()->camera_controller()->RemoveObserver(this);
}
gfx::Rect CaptureModeSettingsView::GetBounds(
CaptureModeBarView* capture_mode_bar_view,
CaptureModeSettingsView* content_view) {
DCHECK(capture_mode_bar_view);
const gfx::Size settings_size =
content_view ? content_view->GetPreferredSize() : kSettingsSize;
return GetWidgetBounds(capture_mode_bar_view, settings_size);
}
void CaptureModeSettingsView::OnCaptureFolderMayHaveChanged() {
if (!save_to_menu_group_)
return;
auto* controller = CaptureModeController::Get();
const auto custom_path = controller->GetCustomCaptureFolder();
if (custom_path.empty()) {
is_custom_folder_available_.reset();
save_to_menu_group_->RemoveOptionIfAny(kCustomFolder);
save_to_menu_group_->RefreshOptionsSelections();
return;
}
std::u16string folder_name = custom_path.BaseName().AsUTF16Unsafe();
// We explicitly name the folders of Google Drive and Play files, since those
// folders internally may have user-unfriendly names.
if (controller->IsRootDriveFsPath(custom_path)) {
folder_name =
l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_SAVE_TO_GOOGLE_DRIVE);
} else if (controller->IsAndroidFilesPath(custom_path)) {
folder_name =
l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_SAVE_TO_ANDROID_FILES);
} else if (controller->IsLinuxFilesPath(custom_path)) {
folder_name =
l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_SAVE_TO_LINUX_FILES);
}
save_to_menu_group_->AddOrUpdateExistingOption(folder_name, kCustomFolder);
controller->CheckFolderAvailability(
custom_path,
base::BindOnce(
&CaptureModeSettingsView::OnCustomFolderAvailabilityChecked,
weak_ptr_factory_.GetWeakPtr()));
}
void CaptureModeSettingsView::OnDefaultCaptureFolderSelectionChanged() {
if (save_to_menu_group_)
save_to_menu_group_->RefreshOptionsSelections();
}
std::vector<CaptureModeSessionFocusCycler::HighlightableView*>
CaptureModeSettingsView::GetHighlightableItems() {
std::vector<CaptureModeSessionFocusCycler::HighlightableView*>
highlightable_items;
DCHECK(audio_input_menu_group_);
audio_input_menu_group_->AppendHighlightableItems(highlightable_items);
if (features::IsCaptureModeSelfieCameraEnabled()) {
DCHECK(camera_menu_group_);
camera_menu_group_->AppendHighlightableItems(highlightable_items);
}
if (save_to_menu_group_)
save_to_menu_group_->AppendHighlightableItems(highlightable_items);
return highlightable_items;
}
void CaptureModeSettingsView::OnOptionSelected(int option_id) const {
auto* controller = CaptureModeController::Get();
auto* camera_controller = controller->camera_controller();
switch (option_id) {
case kAudioOff:
controller->EnableAudioRecording(false);
break;
case kAudioMicrophone:
controller->EnableAudioRecording(true);
break;
case kDownloadsFolder:
controller->SetUsesDefaultCaptureFolder(true);
RecordSwitchToDefaultFolderReason(
CaptureModeSwitchToDefaultReason::kUserSelectedFromSettingsMenu);
break;
case kCustomFolder:
controller->SetUsesDefaultCaptureFolder(false);
break;
case kCameraOff:
camera_controller->SetSelectedCamera(CameraId());
break;
default:
DCHECK(!camera_controller->IsCameraDisabledByPolicy());
DCHECK_GE(option_id, kCameraDevicesBegin);
const CameraId* camera_id = FindCameraIdByOptionId(option_id);
DCHECK(camera_id);
camera_controller->SetSelectedCamera(*camera_id);
break;
}
}
bool CaptureModeSettingsView::IsOptionChecked(int option_id) const {
auto* controller = CaptureModeController::Get();
auto* camera_controller = controller->camera_controller();
switch (option_id) {
case kAudioOff:
return !CaptureModeController::Get()->enable_audio_recording();
case kAudioMicrophone:
return CaptureModeController::Get()->enable_audio_recording();
case kDownloadsFolder:
return GetCurrentCaptureFolder().is_default_downloads_folder ||
!is_custom_folder_available_.value_or(false);
case kCustomFolder:
return !GetCurrentCaptureFolder().is_default_downloads_folder &&
is_custom_folder_available_.value_or(false);
case kCameraOff:
return !camera_controller->selected_camera().is_valid();
default:
DCHECK(!camera_controller->IsCameraDisabledByPolicy());
DCHECK_GE(option_id, kCameraDevicesBegin);
const CameraId* camera_id = FindCameraIdByOptionId(option_id);
DCHECK(camera_id);
return *camera_id == camera_controller->selected_camera();
}
}
bool CaptureModeSettingsView::IsOptionEnabled(int option_id) const {
switch (option_id) {
case kAudioOff:
return !capture_mode_session_->is_in_projector_mode();
case kCustomFolder:
return is_custom_folder_available_.value_or(false);
case kCameraOff: {
auto* camera_controller =
CaptureModeController::Get()->camera_controller();
DCHECK(camera_controller);
return !camera_controller->IsCameraDisabledByPolicy();
}
case kAudioMicrophone:
case kDownloadsFolder:
default:
return true;
}
}
void CaptureModeSettingsView::OnAvailableCamerasChanged(
const CameraInfoList& cameras) {
auto* controller = CaptureModeController::Get();
DCHECK(!controller->is_recording_in_progress());
DCHECK(camera_menu_group_);
auto* camera_controller = controller->camera_controller();
DCHECK(camera_controller);
AddCameraOptions(cameras, camera_controller->IsCameraDisabledByPolicy());
// If the size of the given `cameras` is equal to the size of the current
// available cameras, the bounds of the `camera_menu_group_` won't be updated,
// hence a layout may not be triggered. This can cause the newly added camera
// options to be not visible. We must guarantee that a layout will always
// occur by invalidating the layout.
camera_menu_group_->InvalidateLayout();
camera_menu_group_->RefreshOptionsSelections();
capture_mode_session_->MaybeUpdateSettingsBounds();
}
void CaptureModeSettingsView::OnSelectedCameraChanged(
const CameraId& camera_id) {
// TODO(conniekxu): Implement this function.
}
views::View* CaptureModeSettingsView::GetMicrophoneOptionForTesting() {
return audio_input_menu_group_->GetOptionForTesting( // IN-TEST
kAudioMicrophone); // IN-TEST
}
views::View* CaptureModeSettingsView::GetOffOptionForTesting() {
return audio_input_menu_group_->GetOptionForTesting(kAudioOff); // IN-TEST
}
void CaptureModeSettingsView::OnSelectFolderMenuItemPressed() {
capture_mode_session_->OpenFolderSelectionDialog();
}
void CaptureModeSettingsView::OnCustomFolderAvailabilityChecked(
bool available) {
DCHECK(save_to_menu_group_);
is_custom_folder_available_ = available;
save_to_menu_group_->RefreshOptionsSelections();
if (!is_custom_folder_available_.value_or(false)) {
RecordSwitchToDefaultFolderReason(
CaptureModeSwitchToDefaultReason::kFolderUnavailable);
}
if (on_settings_menu_refreshed_callback_for_test_)
std::move(on_settings_menu_refreshed_callback_for_test_).Run();
}
const CameraId* CaptureModeSettingsView::FindCameraIdByOptionId(
int option_id) const {
auto target_it = option_camera_id_map_.find(option_id);
if (target_it != option_camera_id_map_.end())
return &(target_it->second);
return nullptr;
}
void CaptureModeSettingsView::AddCameraOptions(const CameraInfoList& cameras,
bool managed_by_policy) {
DCHECK(camera_menu_group_);
camera_menu_group_->DeleteOptions();
option_camera_id_map_.clear();
const bool has_cameras = !cameras.empty();
if (has_cameras) {
camera_menu_group_->AddOption(
l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_CAMERA_OFF),
kCameraOff);
if (!managed_by_policy) {
int camera_option_id_begin = kCameraDevicesBegin;
for (const CameraInfo& camera_info : cameras) {
option_camera_id_map_[camera_option_id_begin] = camera_info.camera_id;
camera_menu_group_->AddOption(
base::UTF8ToUTF16(camera_info.display_name),
camera_option_id_begin++);
}
}
}
UpdateCameraMenuGroupVisibility(/*visible=*/has_cameras);
}
void CaptureModeSettingsView::UpdateCameraMenuGroupVisibility(bool visible) {
separator_1_->SetVisible(visible);
camera_menu_group_->SetVisible(visible);
}
BEGIN_METADATA(CaptureModeSettingsView, views::View)
END_METADATA
} // namespace ash