chromium/ash/curtain/session.cc

// 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"
#include "ui/ozone/public/input_controller.h"
#include "ui/ozone/public/ozone_platform.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)));
  }
  if (init_params.disable_input_devices) {
    scoped_input_devices_disabler_ =
        CHECK_DEREF(ui::OzonePlatform::GetInstance()->GetInputController())
            .DisableInputDevices();
  }

  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_.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