chromium/chrome/browser/ui/ash/network/network_portal_notification_controller.cc

// Copyright 2014 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/ui/ash/network/network_portal_notification_controller.h"

#include <stdint.h>

#include <memory>
#include <string>
#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/public/cpp/notification_utils.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ash/mobile/mobile_activator.h"
#include "chrome/browser/notifications/notification_handler.h"
#include "chrome/browser/notifications/system_notification_helper.h"
#include "chrome/browser/ui/ash/network/network_portal_signin_controller.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_type_pattern.h"
#include "components/session_manager/core/session_manager.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_types.h"
#include "ui/message_center/public/cpp/notifier_id.h"

namespace ash {

namespace {

const char kNotifierNetworkPortalDetector[] = "ash.network.portal-detector";

bool IsPortalState(NetworkState::PortalState portal_state) {
  return portal_state == NetworkState::PortalState::kPortal ||
         portal_state == NetworkState::PortalState::kPortalSuspected;
}

std::unique_ptr<message_center::Notification> CreateNotification(
    const NetworkState* network,
    scoped_refptr<message_center::NotificationDelegate> delegate,
    message_center::NotifierId notifier_id,
    bool is_wifi,
    NetworkState::PortalState portal_state) {
  int message;
  message_center::ButtonInfo button;
  message_center::RichNotificationData data;
  switch (portal_state) {
    case NetworkState::PortalState::kPortal:
    case NetworkState::PortalState::kPortalSuspected:
      message = IDS_NEW_PORTAL_DETECTION_NOTIFICATION_MESSAGE;
      button.title = l10n_util::GetStringUTF16(
          IDS_NEW_PORTAL_DETECTION_NOTIFICATION_BUTTON);
      data.buttons.emplace_back(std::move(button));
      break;
    default:
      NOTREACHED_IN_MIGRATION();
      return nullptr;
  }

  std::unique_ptr<message_center::Notification> notification =
      CreateSystemNotificationPtr(
          message_center::NOTIFICATION_TYPE_SIMPLE,
          NetworkPortalNotificationController::kNotificationId,
          l10n_util::GetStringUTF16(
              is_wifi ? IDS_NEW_PORTAL_DETECTION_NOTIFICATION_TITLE_WIFI
                      : IDS_NEW_PORTAL_DETECTION_NOTIFICATION_TITLE_WIRED),
          l10n_util::GetStringFUTF16(message,
                                     base::UTF8ToUTF16(network->name())),
          /*display_source=*/std::u16string(), /*origin_url=*/GURL(),
          notifier_id, data, std::move(delegate),
          kNotificationCaptivePortalIcon,
          message_center::SystemNotificationWarningLevel::NORMAL);
  notification->set_never_timeout(true);
  return notification;
}

void CloseNotification() {
  SystemNotificationHelper::GetInstance()->Close(
      NetworkPortalNotificationController::kNotificationId);
}

}  // namespace

class NotificationDelegateImpl : public message_center::NotificationDelegate {
 public:
  NotificationDelegateImpl() = default;
  NotificationDelegateImpl(const NotificationDelegateImpl&) = delete;
  NotificationDelegateImpl& operator=(const NotificationDelegateImpl&) = delete;

  // message_center::NotificationDelegate
  void Click(const std::optional<int>& button_index,
             const std::optional<std::u16string>& reply) override;

 private:
  ~NotificationDelegateImpl() override = default;
};

void NotificationDelegateImpl::Click(
    const std::optional<int>& button_index,
    const std::optional<std::u16string>& reply) {
  NET_LOG(USER) << "Captive Portal notification: Click";
  NetworkPortalSigninController::Get()->ShowSignin(
      NetworkPortalSigninController::SigninSource::kNotification);
  CloseNotification();
}

// static
const char NetworkPortalNotificationController::kNotificationId[] =
    "chrome://net/network_portal_detector";

NetworkPortalNotificationController::NetworkPortalNotificationController() {
  if (NetworkHandler::IsInitialized())  // May be null in tests.
    NetworkHandler::Get()->network_state_handler()->AddObserver(this);
  DCHECK(session_manager::SessionManager::Get());
  session_manager::SessionManager::Get()->AddObserver(this);
}

NetworkPortalNotificationController::~NetworkPortalNotificationController() {
  if (NetworkHandler::IsInitialized()) {
    NetworkHandler::Get()->network_state_handler()->RemoveObserver(this);
  }
  if (session_manager::SessionManager::Get())
    session_manager::SessionManager::Get()->RemoveObserver(this);
}

void NetworkPortalNotificationController::PortalStateChanged(
    const NetworkState* network,
    NetworkState::PortalState portal_state) {
  if (!network || !IsPortalState(portal_state)) {
    if (!last_network_guid_.empty() && IsPortalState(last_portal_state_)) {
      NET_LOG(EVENT) << "Captive Portal notification: Close for "
                     << last_network_guid_;
    }
    last_network_guid_.clear();
    last_portal_state_ = portal_state;

    // In browser tests we initiate fake network portal detection, but network
    // state usually stays connected. This way, after dialog is shown, it is
    // immediately closed. The testing check below prevents dialog from closing.
    if (!ignore_no_network_for_testing_ ||
        portal_state == NetworkState::PortalState::kOnline) {
      NetworkPortalSigninController::Get()->CloseSignin();
    }

    CloseNotification();
    return;
  }

  // Don't do anything if we're currently activating the device.
  if (MobileActivator::GetInstance()->RunningActivation()) {
    NET_LOG(EVENT) << "Captive Portal notification: Skip (mobile activation)";
    return;
  }

  // Don't do anything if notification for |network| already was
  // displayed with the same portal_state.
  if (network->guid() == last_network_guid_ &&
      portal_state == last_portal_state_) {
    return;
  }
  last_network_guid_ = network->guid();
  last_portal_state_ = portal_state;

  NET_LOG(EVENT) << "Captive Portal notification: Show for "
                 << NetworkId(network) << " PortalState: " << portal_state;
  base::UmaHistogramEnumeration("Network.NetworkPortalNotificationState",
                                portal_state);

  std::unique_ptr<message_center::Notification> notification =
      CreateDefaultCaptivePortalNotification(network, portal_state);
  DCHECK(notification) << "Notification not created for portal state: "
                       << portal_state;
  SystemNotificationHelper::GetInstance()->Display(*notification);
}

void NetworkPortalNotificationController::OnShuttingDown() {
  NetworkPortalSigninController::Get()->CloseSignin();
  NetworkHandler::Get()->network_state_handler()->RemoveObserver(this);
}

void NetworkPortalNotificationController::OnSessionStateChanged() {
  TRACE_EVENT0("ui",
               "NetworkPortalNotificationController::OnSessionStateChanged");
  session_manager::SessionState state =
      session_manager::SessionManager::Get()->session_state();
  if (state == session_manager::SessionState::LOCKED) {
    NetworkPortalSigninController::Get()->CloseSignin();
  }
}

std::unique_ptr<message_center::Notification>
NetworkPortalNotificationController::CreateDefaultCaptivePortalNotification(
    const NetworkState* network,
    NetworkState::PortalState portal_state) {
  auto notification_delegate = base::MakeRefCounted<NotificationDelegateImpl>();
  message_center::NotifierId notifier_id(
      message_center::NotifierType::SYSTEM_COMPONENT,
      kNotifierNetworkPortalDetector,
      NotificationCatalogName::kNetworkPortalDetector);
  bool is_wifi = NetworkTypePattern::WiFi().MatchesType(network->type());
  std::unique_ptr<message_center::Notification> notification;
  notification = CreateNotification(network, notification_delegate, notifier_id,
                                    is_wifi, portal_state);
  return notification;
}

void NetworkPortalNotificationController::SetIgnoreNoNetworkForTesting() {
  ignore_no_network_for_testing_ = true;
}

}  // namespace ash