chromium/ash/system/privacy/screen_security_controller.cc

// Copyright 2018 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/system/privacy/screen_security_controller.h"

#include "ash/constants/ash_constants.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/public/cpp/notification_utils.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/privacy/privacy_indicators_controller.h"
#include "ash/system/tray/system_tray_notifier.h"
#include "base/functional/bind.h"
#include "base/metrics/user_metrics.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/color/color_id.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_delegate.h"

using message_center::MessageCenter;
using message_center::Notification;

namespace ash {

// It is possible that we are capturing and sharing screen at the same time, so
// we cannot share the notification IDs for capturing and sharing.
const char kScreenAccessNotificationId[] = "chrome://screen/access";
const char kRemotingScreenShareNotificationId[] =
    "chrome://screen/remoting-share";

ScreenSecurityController::ScreenSecurityController() {
  Shell::Get()->AddShellObserver(this);
  Shell::Get()->system_tray_notifier()->AddScreenSecurityObserver(this);
}

ScreenSecurityController::~ScreenSecurityController() {
  Shell::Get()->system_tray_notifier()->RemoveScreenSecurityObserver(this);
  Shell::Get()->RemoveShellObserver(this);
}

void ScreenSecurityController::StopAllSessions(bool is_screen_access) {
  message_center::MessageCenter::Get()->RemoveNotification(
      is_screen_access ? kScreenAccessNotificationId
                       : kRemotingScreenShareNotificationId,
      /*by_user=*/false);

  std::vector<base::OnceClosure> callbacks;
  std::swap(callbacks, is_screen_access ? screen_access_stop_callbacks_
                                        : remoting_share_stop_callbacks_);
  for (base::OnceClosure& callback : callbacks) {
    if (callback) {
      std::move(callback).Run();
    }
  }

  change_source_callback_.Reset();
}

void ScreenSecurityController::CreateNotification(
    const std::u16string& message,
    bool is_screen_access_notification) {
  if (features::IsVideoConferenceEnabled()) {
    // Don't send screen share notifications, because the VideoConferenceTray
    // serves as the notifier for screen share. As for screen capture, continue
    // to show these notifications for now, although they may end up in the
    // `VideoConferenceTray` as well. See b/269486186 for details.
    DCHECK(is_screen_access_notification);
  }

  message_center::RichNotificationData data;
  data.buttons.emplace_back(
      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SCREEN_ACCESS_STOP));
  // Only add "Change source" button when there is one session, since there
  // isn't a good UI to distinguish between the different sessions.
  if (is_screen_access_notification && change_source_callback_ &&
      screen_access_stop_callbacks_.size() == 1) {
    data.buttons.emplace_back(l10n_util::GetStringUTF16(
        IDS_ASH_STATUS_TRAY_SCREEN_CAPTURE_CHANGE_SOURCE));
  }

  auto delegate =
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          base::BindRepeating(
              [](base::WeakPtr<ScreenSecurityController> controller,
                 bool is_screen_access_notification,
                 std::optional<int> button_index) {
                if (!button_index)
                  return;

                if (*button_index == 0) {
                  controller->StopAllSessions(
                      /*is_screen_access=*/is_screen_access_notification);
                } else if (*button_index == 1) {
                  controller->ChangeSource();
                  if (is_screen_access_notification) {
                    base::RecordAction(base::UserMetricsAction(
                        "StatusArea_ScreenCapture_Change_Source"));
                  }
                } else {
                  NOTREACHED();
                }
              },
              weak_ptr_factory_.GetWeakPtr(), is_screen_access_notification));

  std::unique_ptr<Notification> notification = CreateSystemNotificationPtr(
      message_center::NOTIFICATION_TYPE_SIMPLE,
      is_screen_access_notification ? kScreenAccessNotificationId
                                    : kRemotingScreenShareNotificationId,
      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SCREEN_SHARE_TITLE),
      message, std::u16string() /* display_source */, GURL(),
      message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
                                 kPrivacyIndicatorsNotifierId,
                                 NotificationCatalogName::kPrivacyIndicators),
      data, std::move(delegate),
      /*small_image=*/kPrivacyIndicatorsScreenShareIcon,
      message_center::SystemNotificationWarningLevel::NORMAL);

  notification->set_pinned(true);
  notification->set_accent_color_id(ui::kColorAshPrivacyIndicatorsBackground);
  notification->set_parent_vector_small_image(kPrivacyIndicatorsIcon);

  message_center::MessageCenter::Get()->AddNotification(
      std::move(notification));
}

void ScreenSecurityController::ChangeSource() {
  if (change_source_callback_ && screen_access_stop_callbacks_.size() == 1) {
    change_source_callback_.Run();
  }
}

void ScreenSecurityController::OnScreenAccessStart(
    base::OnceClosure stop_callback,
    const base::RepeatingClosure& source_callback,
    const std::u16string& access_app_name) {
  screen_access_stop_callbacks_.emplace_back(std::move(stop_callback));
  change_source_callback_ = source_callback;

  // Don't send screen access notifications, because the VideoConferenceTray
  // serves as the notifier for this.
  if (features::IsVideoConferenceEnabled()) {
    return;
  }

  // We do not want to show the screen capture notification and the chromecast
  // casting tray notification at the same time.
  //
  // This suppression technique is currently dependent on the order
  // that OnScreenCaptureStart and OnCastingSessionStartedOrStopped
  // get invoked. OnCastingSessionStartedOrStopped currently gets
  // called first.
  if (is_casting_)
    return;

  CreateNotification(access_app_name, /*is_screen_access_notification=*/true);
  UpdatePrivacyIndicatorsScreenShareStatus(/*is_screen_sharing=*/true);
}

void ScreenSecurityController::OnScreenAccessStop() {
  if (features::IsVideoConferenceEnabled()) {
    return;
  }

  StopAllSessions(/*is_screen_access=*/true);
  UpdatePrivacyIndicatorsScreenShareStatus(/*is_screen_sharing=*/false);
}

void ScreenSecurityController::OnRemotingScreenShareStart(
    base::OnceClosure stop_callback) {
  remoting_share_stop_callbacks_.emplace_back(std::move(stop_callback));

  // Don't send screen share notifications, because the VideoConferenceTray
  // serves as the notifier for screen share.
  if (features::IsVideoConferenceEnabled()) {
    return;
  }

  CreateNotification(
      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SCREEN_SHARE_BEING_HELPED),
      /*is_screen_access_notification=*/false);
  UpdatePrivacyIndicatorsScreenShareStatus(/*is_screen_sharing=*/true);
}

void ScreenSecurityController::OnRemotingScreenShareStop() {
  if (features::IsVideoConferenceEnabled()) {
    return;
  }

  StopAllSessions(/*is_screen_access=*/false);
  UpdatePrivacyIndicatorsScreenShareStatus(/*is_screen_sharing=*/false);
}

void ScreenSecurityController::OnCastingSessionStartedOrStopped(bool started) {
  is_casting_ = started;
}

}  // namespace ash