chromium/chrome/browser/ash/notifications/multi_capture_notifications.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 "chrome/browser/ash/notifications/multi_capture_notifications.h"

#include <memory>
#include <optional>

#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 "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/safe_ref.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/media/webrtc/capture_policy_utils.h"
#include "chrome/browser/notifications/system_notification_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_context.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/text_constants.h"
#include "ui/gfx/text_elider.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notifier_id.h"
#include "url/origin.h"

namespace ash {

namespace {

constexpr size_t kAppMaxNameLength = 18;

constexpr char kMultiCaptureId[] = "multi_capture";
constexpr char kNotifierMultiCapture[] = "ash.multi_capture";
constexpr char kMultiCaptureOnLoginId[] = "multi_capture_on_login";
constexpr char kNotifierMultiCaptureOnLogin[] = "ash.multi_capture_on_login";

constexpr base::TimeDelta kMinimumNotificationPresenceTime = base::Seconds(6);

void CreateAndShowNotification(
    const std::string& notifier_id,
    const std::string& notification_id,
    NotificationCatalogName notification_catalog_name,
    std::u16string notification_title,
    std::u16string notification_message) {
  message_center::NotifierId notifier(
      message_center::NotifierType::SYSTEM_COMPONENT, notifier_id,
      notification_catalog_name);

  message_center::Notification notification = CreateSystemNotification(
      message_center::NOTIFICATION_TYPE_SIMPLE, notification_id,
      /*title=*/
      notification_title,
      /*message=*/
      notification_message,
      /*display_source=*/std::u16string(), /*origin_url=*/GURL(), notifier,
      /*optional_fields=*/message_center::RichNotificationData(),
      /*delegate=*/
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          message_center::HandleNotificationClickDelegate::ButtonClickCallback(
              base::DoNothing())),
      kShelfEnterpriseIcon,
      message_center::SystemNotificationWarningLevel::NORMAL);
  SystemNotificationHelper::GetInstance()->Display(notification);
}

void MaybeShowLoginNotification(bool is_multi_capture_allowed) {
  if (!is_multi_capture_allowed) {
    return;
  }
  CreateAndShowNotification(
      kNotifierMultiCaptureOnLogin, kMultiCaptureOnLoginId,
      NotificationCatalogName::kMultiCaptureOnLogin,
      l10n_util::GetStringUTF16(IDS_MULTI_CAPTURE_NOTIFICATION_ON_LOGIN_TITLE),
      l10n_util::GetStringUTF16(
          IDS_MULTI_CAPTURE_NOTIFICATION_ON_LOGIN_MESSAGE));
}

// This function makes sure that on login all data required to check whether a
// notification is needed is propagated from the policy to the
// ManagedAccessToGetAllScreensMediaInSessionAllowedForUrls pref.
void TransferGetAllScreensMediaPolicyValue(
    content::BrowserContext* browser_context) {
  DCHECK(browser_context);
  Profile* profile = Profile::FromBrowserContext(browser_context);
  PrefService* pref_service = profile->GetPrefs();
  if (!pref_service) {
    return;
  }
  const base::Value::List& allowed_origins = pref_service->GetList(
      capture_policy::kManagedAccessToGetAllScreensMediaAllowedForUrls);
  pref_service->SetList(
      prefs::kManagedAccessToGetAllScreensMediaInSessionAllowedForUrls,
      allowed_origins.Clone());
}

void ShowLoginNotificationIfMultiCaptureAllowed() {
  auto* active_user = user_manager::UserManager::Get()->GetActiveUser();
  if (!active_user) {
    return;
  }

  content::BrowserContext* browser_context =
      BrowserContextHelper::Get()->GetBrowserContextByUser(active_user);
  if (!browser_context) {
    return;
  }

  // TODO(b/329064666): Remove this function once the pivot to IWAs is complete.
  TransferGetAllScreensMediaPolicyValue(browser_context);

  capture_policy::CheckGetAllScreensMediaAllowedForAnyOrigin(
      browser_context, base::BindOnce(&MaybeShowLoginNotification));
}

}  // namespace

MultiCaptureNotifications::NotificationMetadata::NotificationMetadata(
    std::string id,
    base::TimeTicks time_created)
    : id(std::move(id)), time_created(time_created) {}
MultiCaptureNotifications::NotificationMetadata::NotificationMetadata(
    MultiCaptureNotifications::NotificationMetadata&& metadata) = default;
MultiCaptureNotifications::NotificationMetadata&
MultiCaptureNotifications::NotificationMetadata::operator=(
    MultiCaptureNotifications::NotificationMetadata&& metadata) = default;
MultiCaptureNotifications::NotificationMetadata::~NotificationMetadata() =
    default;

MultiCaptureNotifications::MultiCaptureNotifications() {
  DCHECK(Shell::HasInstance());
  multi_capture_service_client_observation_.Observe(
      Shell::Get()->multi_capture_service_client());
  login_state_observation_.Observe(LoginState::Get());
}

MultiCaptureNotifications::~MultiCaptureNotifications() = default;

void MultiCaptureNotifications::MultiCaptureStarted(const std::string& label,
                                                    const url::Origin& origin) {
  const std::string host = origin.host();
  MultiCaptureStartedInternal(label, base::StrCat({kMultiCaptureId, ":", host}),
                              host);
}

void MultiCaptureNotifications::MultiCaptureStartedFromApp(
    const std::string& label,
    const std::string& app_id,
    const std::string& app_short_name) {
  MultiCaptureStartedInternal(
      label, base::StrCat({kMultiCaptureId, ":", label}), app_short_name);
}

void MultiCaptureNotifications::MultiCaptureStopped(const std::string& label) {
  const auto notifications_metadata_iterator =
      notifications_metadata_.find(label);
  if (notifications_metadata_iterator == notifications_metadata_.end()) {
    LOG(ERROR) << "Label could not be found";
    return;
  }

  NotificationMetadata& metadata = notifications_metadata_iterator->second;
  const base::TimeDelta time_already_shown =
      base::TimeTicks::Now() - metadata.time_created;
  if (time_already_shown >= kMinimumNotificationPresenceTime) {
    SystemNotificationHelper::GetInstance()->Close(
        /*notification_id=*/metadata.id);
    notifications_metadata_.erase(notifications_metadata_iterator);
  } else if (!metadata.closing_timer) {
    metadata.closing_timer = std::make_unique<base::OneShotTimer>();
    metadata.closing_timer->Start(
        FROM_HERE, kMinimumNotificationPresenceTime - time_already_shown,
        base::BindOnce(&MultiCaptureNotifications::MultiCaptureStopped,
                       weak_factory_.GetSafeRef(), label));
  }
}

void MultiCaptureNotifications::MultiCaptureServiceClientDestroyed() {
  multi_capture_service_client_observation_.Reset();
  for (const auto& [label, notification_metadata] : notifications_metadata_) {
    SystemNotificationHelper::GetInstance()->Close(
        /*notification_id=*/notification_metadata.id);
  }
  notifications_metadata_.clear();
}

void MultiCaptureNotifications::LoggedInStateChanged() {
  if (LoginState::Get()->IsUserLoggedIn()) {
    user_manager::User* active_user =
        user_manager::UserManager::Get()->GetActiveUser();
    if (!active_user) {
      return;
    }

    active_user->AddProfileCreatedObserver(
        base::BindOnce(&ShowLoginNotificationIfMultiCaptureAllowed));
  }
}

void MultiCaptureNotifications::MultiCaptureStartedInternal(
    const std::string& label,
    const std::string& notification_id,
    const std::string& app_name) {
  notifications_metadata_.emplace(
      label, NotificationMetadata(notification_id, base::TimeTicks::Now()));

  const std::u16string converted_app_name =
      gfx::TruncateString(base::UTF8ToUTF16(app_name), kAppMaxNameLength,
                          gfx::BreakType::WORD_BREAK);

  // TODO(crbug.com/40236161): Make sure the notification does not disappear
  // automatically after some time.
  CreateAndShowNotification(
      kNotifierMultiCapture, notification_id,
      NotificationCatalogName::kMultiCapture,
      /*notification_title=*/
      l10n_util::GetStringFUTF16(IDS_MULTI_CAPTURE_NOTIFICATION_TITLE,
                                 converted_app_name),
      /*notification_message=*/
      l10n_util::GetStringFUTF16(IDS_MULTI_CAPTURE_NOTIFICATION_MESSAGE,
                                 converted_app_name));
}

}  // namespace ash