chromium/ash/display/privacy_screen_controller.cc

// Copyright 2020 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/display/privacy_screen_controller.h"

#include "ash/constants/ash_pref_names.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "ui/display/manager/display_configurator.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/types/display_snapshot.h"

namespace ash {

namespace {

// Gets the DisplaySnapshot of the internal display that supports privacy
// screen. Returns nullptr if none exists.
display::DisplaySnapshot* GetSupportedDisplay() {
  const auto& cached_displays =
      Shell::Get()->display_configurator()->cached_displays();

  for (display::DisplaySnapshot* display : cached_displays) {
    if (display->type() == display::DISPLAY_CONNECTION_TYPE_INTERNAL &&
        display->privacy_screen_state() != display::kNotSupported &&
        display->current_mode()) {
      return display;
    }
  }
  return nullptr;
}

// Gets the ID of the internal display that supports privacy screen. Returns
// display::kInvalidDisplayId if none is found.
int64_t GetSupportedDisplayId() {
  auto* privacy_screen_display = GetSupportedDisplay();
  return privacy_screen_display ? privacy_screen_display->display_id()
                                : display::kInvalidDisplayId;
}

}  // namespace

PrivacyScreenController::PrivacyScreenController() {
  Shell::Get()->session_controller()->AddObserver(this);
  Shell::Get()->display_configurator()->AddObserver(this);
}

PrivacyScreenController::~PrivacyScreenController() {
  Shell::Get()->display_configurator()->RemoveObserver(this);
  Shell::Get()->session_controller()->RemoveObserver(this);
}

// static
void PrivacyScreenController::RegisterProfilePrefs(
    PrefRegistrySimple* registry) {
  registry->RegisterBooleanPref(prefs::kDisplayPrivacyScreenEnabled, false);
}

bool PrivacyScreenController::IsSupported() const {
  return GetSupportedDisplayId() != display::kInvalidDisplayId;
}

bool PrivacyScreenController::IsManaged() const {
  return dlp_enforced_ || (active_user_pref_service_ &&
                           active_user_pref_service_->IsManagedPreference(
                               prefs::kDisplayPrivacyScreenEnabled));
}

bool PrivacyScreenController::GetEnabled() const {
  return current_status_;
}

void PrivacyScreenController::SetEnabled(bool enabled) {
  if (!IsSupported()) {
    LOG(ERROR) << "Attempted to set privacy-screen on an unsupported device.";
    return;
  }

  // Do not set the policy if it is managed by policy. However, we still want to
  // notify observers that a change was attempted in order to show a toast.
  if (IsManaged()) {
    const bool currently_enabled = GetEnabled();
    for (Observer& observer : observers_)
      observer.OnPrivacyScreenSettingChanged(currently_enabled,
                                             /*notify_ui=*/true);
    return;
  }

  if (active_user_pref_service_) {
    if (GetStateFromActiveUserPreference() == enabled) {
      // Since it is possible for DRM to fail a privacy screen hardware toggle,
      // following calls to SetEnabled() may try to set the user pref to a state
      // it is already set to. This will end up as a NOP for such SetEnabled()
      // calls. Therefore, we manually trigger a call to OnStateChanged here to
      // allow following toggle attempts to get through to DRM.
      OnStateChanged(/*from_user_pref_init=*/false);
    } else {
      active_user_pref_service_->SetBoolean(prefs::kDisplayPrivacyScreenEnabled,
                                            enabled);
    }
  }
}

void PrivacyScreenController::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void PrivacyScreenController::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

void PrivacyScreenController::SetEnforced(bool enforced) {
  // Only send a toast to the user if policy has changed the state of the
  // privacy screen.
  dlp_enforced_ = enforced;
  OnStateChanged(/*from_user_pref_init=*/false);
}

void PrivacyScreenController::OnActiveUserPrefServiceChanged(
    PrefService* pref_service) {
  active_user_pref_service_ = pref_service;
  InitFromUserPrefs();
}

void PrivacyScreenController::OnSigninScreenPrefServiceInitialized(
    PrefService* prefs) {
  active_user_pref_service_ = prefs;

  // The privacy screen is toggled via commands to the GPU process, which is
  // initialized after the signin screen emits this event. Therefore we must
  // wait for OnDisplayModeChanged() to notify us when the display configuration
  // is ready, which implies that the GPU process and communication pipes are
  // ready.
  applying_login_screen_prefs_ = true;
}

void PrivacyScreenController::OnDisplayConfigurationChanged(
    const std::vector<raw_ptr<display::DisplaySnapshot, VectorExperimental>>&
        displays) {
  // OnDisplayConfigurationChanged() may fire many times during Chrome's
  // lifetime. We limit automatic user pref initialization to login screen only.
  if (!applying_login_screen_prefs_)
    return;

  // Extract the initial state of the privacy screen from the supporting panel
  // at the time the display was configured.
  display::DisplaySnapshot* privacy_screen_display = GetSupportedDisplay();
  current_status_ =
      privacy_screen_display &&
      (privacy_screen_display->privacy_screen_state() == display::kEnabled ||
       privacy_screen_display->privacy_screen_state() ==
           display::kEnabledLocked);

  InitFromUserPrefs();
  applying_login_screen_prefs_ = false;
}

bool PrivacyScreenController::CalculateCurrentStatus() const {
  if (!active_user_pref_service_)
    return dlp_enforced_;
  const bool actual_user_pref = GetStateFromActiveUserPreference();
  // If managed by policy, return the pref value.
  if (active_user_pref_service_->IsManagedPreference(
          prefs::kDisplayPrivacyScreenEnabled)) {
    return actual_user_pref;
  }
  // Otherwise return true if enforced by DLP or return the last state set by
  // the user.
  return dlp_enforced_ || actual_user_pref;
}

void PrivacyScreenController::OnStateChanged(bool from_user_pref_init) {
  const int64_t display_id = GetSupportedDisplayId();
  if (display_id == display::kInvalidDisplayId)
    return;

  const bool enable_screen = CalculateCurrentStatus();
  if (enable_screen == current_status_)
    return;

  Shell::Get()->display_configurator()->SetPrivacyScreen(
      display_id, enable_screen,
      base::BindOnce(&PrivacyScreenController::OnSetPrivacyScreenComplete,
                     weak_ptr_factory_.GetWeakPtr(), from_user_pref_init,
                     enable_screen));
}

void PrivacyScreenController::OnSetPrivacyScreenComplete(
    bool from_user_pref_init,
    bool requested_config,
    bool success) {
  if (success) {
    current_status_ = requested_config;
  } else {
    LOG(ERROR) << "Turning privacy screen " << (requested_config ? "ON" : "OFF")
               << " was unsuccessful.";
  }

  const bool notify_observers = ShouldNotifyObservers(from_user_pref_init);
  for (Observer& observer : observers_)
    observer.OnPrivacyScreenSettingChanged(current_status_, notify_observers);
}

void PrivacyScreenController::InitFromUserPrefs() {
  DCHECK(active_user_pref_service_);

  pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  pref_change_registrar_->Init(active_user_pref_service_);
  pref_change_registrar_->Add(
      prefs::kDisplayPrivacyScreenEnabled,
      base::BindRepeating(&PrivacyScreenController::OnStateChanged,
                          weak_ptr_factory_.GetWeakPtr(),
                          /*from_user_pref_init=*/false));

  OnStateChanged(/*from_user_pref_init=*/true);
}

bool PrivacyScreenController::GetStateFromActiveUserPreference() const {
  return active_user_pref_service_ && active_user_pref_service_->GetBoolean(
                                          prefs::kDisplayPrivacyScreenEnabled);
}

bool PrivacyScreenController::ShouldNotifyObservers(
    bool from_user_pref_init) const {
  // We don't want to notify observers upon initialization or on account change
  // because changes will trigger a toast to show up.
  return !from_user_pref_init;
}

}  // namespace ash