| // 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. |
| |
| #include "ash/curtain/session.h" |
| |
| #include <memory> |
| |
| #include "ash/curtain/security_curtain_widget_controller.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/shell.h" |
| #include "ash/shell_observer.h" |
| #include "ash/system/power/power_button_controller.h" |
| #include "ash/system/privacy_hub/camera_privacy_switch_controller.h" |
| #include "base/check_deref.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/scoped_observation.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "chromeos/ash/components/audio/cras_audio_handler.h" |
| |
| namespace ash::curtain { |
| |
| namespace { |
| |
| // We can only disable the camera if the controller exists, which might |
| // not be the case if the privacy hub feature is disabled. |
| bool CanDisableCamera() { |
| return CameraPrivacySwitchController::Get() != nullptr; |
| } |
| |
| } // namespace |
| //////////////////////////////////////////////////////////////////////////////// |
| // RootWindowsObserver |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| class Session::RootWindowsObserver : public ShellObserver { |
| public: |
| RootWindowsObserver(Session* parent, Shell* shell); |
| RootWindowsObserver(const RootWindowsObserver&) = delete; |
| RootWindowsObserver& operator=(const RootWindowsObserver&) = delete; |
| ~RootWindowsObserver() override; |
| |
| std::vector<display::Display> GetActiveDisplays(Shell& shell) const; |
| |
| private: |
| // ShellObserver implementation: |
| void OnRootWindowAdded(aura::Window* root_window) override; |
| |
| raw_ptr<Session> parent_; |
| |
| base::ScopedObservation<Shell, ShellObserver> shell_observation_{this}; |
| }; |
| |
| Session::RootWindowsObserver::RootWindowsObserver(Session* parent, Shell* shell) |
| : parent_(parent) { |
| shell_observation_.Observe(shell); |
| } |
| |
| Session::RootWindowsObserver::~RootWindowsObserver() = default; |
| |
| void Session::RootWindowsObserver::OnRootWindowAdded( |
| aura::Window* new_root_window) { |
| parent_->CurtainOffRootWindow(new_root_window); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ScopedAudioOutputMuter |
| //////////////////////////////////////////////////////////////////////////////// |
| class Session::ScopedAudioOutputMuter { |
| public: |
| ScopedAudioOutputMuter() { |
| CrasAudioHandler::Get()->SetOutputMuteLockedBySecurityCurtain(true); |
| } |
| |
| ~ScopedAudioOutputMuter() { |
| CrasAudioHandler::Get()->SetOutputMuteLockedBySecurityCurtain(false); |
| } |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ScopedAudioInputMuter |
| //////////////////////////////////////////////////////////////////////////////// |
| class Session::ScopedAudioInputMuter { |
| public: |
| ScopedAudioInputMuter() { |
| CrasAudioHandler::Get()->SetInputMuteLockedBySecurityCurtain(true); |
| } |
| |
| ~ScopedAudioInputMuter() { |
| CrasAudioHandler::Get()->SetInputMuteLockedBySecurityCurtain(false); |
| } |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ScopedCameraDisabler |
| //////////////////////////////////////////////////////////////////////////////// |
| class Session::ScopedCameraDisabler { |
| public: |
| ScopedCameraDisabler() { |
| CHECK_DEREF(CameraPrivacySwitchController::Get()) |
| .SetForceDisableCameraAccess(true); |
| } |
| |
| ~ScopedCameraDisabler() { |
| // Skip cleanup if the shell has been destroyed (so when Chrome is |
| // shutting down). This prevents us from using a half-destroyed `shell_` |
| // object. |
| if (ash::Shell::HasInstance()) { |
| CHECK_DEREF(CameraPrivacySwitchController::Get()) |
| .SetForceDisableCameraAccess(false); |
| } |
| } |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Session |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| Session::Session(Shell* shell, |
| SecurityCurtainController::InitParams init_params) |
| : shell_(*shell), |
| init_params_(init_params), |
| root_windows_observer_( |
| std::make_unique<RootWindowsObserver>(this, shell)) { |
| if (init_params.mute_audio_input) { |
| scoped_audio_input_muter_ = std::make_unique<ScopedAudioInputMuter>(); |
| } |
| if (init_params.disable_camera_access && CanDisableCamera()) { |
| scoped_camera_disabler_ = std::make_unique<ScopedCameraDisabler>(); |
| } |
| if (!init_params.mute_audio_output_after.is_max()) { |
| audio_output_mute_timer_.Start( |
| FROM_HERE, init_params.mute_audio_output_after, |
| base::BindOnce(&Session::MuteAudioOutput, |
| // Safe because `this` owns `audio_output_mute_timer_`. |
| base::Unretained(this))); |
| } |
| |
| CurtainOffAllRootWindows(); |
| shell_->power_button_controller()->OnSecurityCurtainEnabled(); |
| } |
| |
| void Session::Init() { |
| // We must ensure we use a cursor drawn by the software, as a cursor drawn |
| // by the hardware will appear above our curtain which we do not want. |
| // So we tell the shell to tell the cursor manager to ask us if cursor |
| // compositing should be enabled. |
| // This then ends up calling `SecurityCurtainController::IsEnabled()` which is |
| // only true after our constructor is finished, so we must move this in a |
| // separate Init() method instead. |
| shell_->UpdateCursorCompositingEnabled(); |
| } |
| |
| Session::~Session() { |
| // Skip all cleanup if the shell has been destroyed (so when Chrome is |
| // shutting down). This prevents us from using a half-destroyed `shell_` |
| // object. |
| if (ash::Shell::HasInstance()) { |
| RemoveCurtainOfAllRootWindows(); |
| shell_->UpdateCursorCompositingEnabled(); |
| shell_->power_button_controller()->OnSecurityCurtainDisabled(); |
| } |
| } |
| |
| void Session::CurtainOffAllRootWindows() { |
| for (aura::Window* root_window : shell_->GetAllRootWindows()) { |
| CurtainOffRootWindow(root_window); |
| } |
| } |
| |
| void Session::CurtainOffRootWindow(aura::Window* root_window) { |
| DCHECK(root_window->IsRootWindow()); |
| VLOG(1) << "Adding security curtain over root window " << root_window; |
| |
| auto* controller = RootWindowController::ForWindow(root_window); |
| DCHECK(controller); |
| |
| controller->SetSecurityCurtainWidgetController( |
| std::make_unique<SecurityCurtainWidgetController>( |
| SecurityCurtainWidgetController::CreateForRootWindow( |
| root_window, init_params_.event_filter, |
| init_params_.curtain_factory.Run()))); |
| } |
| |
| void Session::RemoveCurtainOfAllRootWindows() { |
| for (aura::Window* root_window : shell_->GetAllRootWindows()) { |
| RemoveCurtainOfRootWindow(root_window); |
| } |
| } |
| |
| void Session::RemoveCurtainOfRootWindow(const aura::Window* root_window) { |
| VLOG(1) << "Removing security curtain from root window " << root_window; |
| |
| auto* controller = RootWindowController::ForWindow(root_window); |
| DCHECK(controller); |
| |
| controller->ClearSecurityCurtainWidgetController(); |
| } |
| |
| void Session::MuteAudioOutput() { |
| scoped_audio_output_muter_ = std::make_unique<ScopedAudioOutputMuter>(); |
| } |
| |
| } // namespace ash::curtain |