chromium/ash/system/bluetooth/bluetooth_notification_controller_unittest.cc

// Copyright 2019 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/bluetooth/bluetooth_notification_controller.h"

#include <memory>
#include <utility>

#include "ash/public/cpp/test/test_nearby_share_delegate.h"
#include "ash/public/cpp/test/test_system_tray_client.h"
#include "ash/session/test_session_controller_client.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/toast/toast_manager_impl.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "ash/test/ash_test_base.h"
#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/message_center/fake_message_center.h"

using testing::NiceMock;
using testing::Return;

namespace ash {
namespace {

const char kTestAdapterName[] = "Chromebook";
const char16_t kTestAdapterName16[] = u"Chromebook";

class TestMessageCenter : public message_center::FakeMessageCenter {
 public:
  TestMessageCenter() = default;

  TestMessageCenter(const TestMessageCenter&) = delete;
  TestMessageCenter& operator=(const TestMessageCenter&) = delete;

  ~TestMessageCenter() override = default;

  void ClickOnNotification(const std::string& id) override {
    message_center::Notification* notification =
        FindVisibleNotificationById(id);
    DCHECK(notification);
    notification->delegate()->Click(std::nullopt, std::nullopt);
  }
};

}  // namespace

class BluetoothNotificationControllerTest : public AshTestBase {
 public:
  BluetoothNotificationControllerTest() = default;

  BluetoothNotificationControllerTest(
      const BluetoothNotificationControllerTest&) = delete;
  BluetoothNotificationControllerTest& operator=(
      const BluetoothNotificationControllerTest&) = delete;

  void SetUp() override {
    AshTestBase::SetUp();

    mock_adapter_ =
        base::MakeRefCounted<NiceMock<device::MockBluetoothAdapter>>();
    ON_CALL(*mock_adapter_, IsPresent()).WillByDefault(Return(true));
    ON_CALL(*mock_adapter_, IsPowered()).WillByDefault(Return(true));
    ON_CALL(*mock_adapter_, GetName()).WillByDefault(Return(kTestAdapterName));
    device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);

    notification_controller_ =
        std::make_unique<BluetoothNotificationController>(
            &test_message_center_);
    system_tray_client_ = GetSystemTrayClient();

    bluetooth_device_1_ =
        std::make_unique<NiceMock<device::MockBluetoothDevice>>(
            mock_adapter_.get(), 0 /* bluetooth_class */, "name_1", "address_1",
            false /* paired */, false /* connected */);
    bluetooth_device_2_ =
        std::make_unique<NiceMock<device::MockBluetoothDevice>>(
            mock_adapter_.get(), 0 /* bluetooth_class */, "name_2", "address_2",
            false /* paired */, false /* connected */);

    toast_manager_ = Shell::Get()->toast_manager();
  }

  void VerifyDiscoverableToastVisibility(bool visible) {
    if (visible) {
      ToastOverlay* overlay = GetCurrentOverlay();
      ASSERT_NE(nullptr, overlay);
      EXPECT_EQ(
          l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERABLE,
                                     kTestAdapterName16),
          overlay->GetText());
    } else {
      EXPECT_EQ(nullptr, GetCurrentOverlay());
    }
  }

  void VerifyPairingNotificationVisibility(bool visible) {
    EXPECT_EQ(test_message_center_.FindVisibleNotificationById(
                  BluetoothNotificationController::
                      kBluetoothDevicePairingNotificationId) != nullptr,
              visible);
  }

  // Run the notification controller to simulate showing a toast.
  void ShowDiscoverableToast(
      BluetoothNotificationController* notification_controller) {
    notification_controller->NotifyAdapterDiscoverable();
  }

  void ShowPairingNotification(
      BluetoothNotificationController* notification_controller,
      device::MockBluetoothDevice* mock_device) {
    notification_controller->AuthorizePairing(mock_device);
  }

  void SimulateDevicePaired(
      BluetoothNotificationController* notification_controller,
      device::MockBluetoothDevice* mock_device) {
    ON_CALL(*mock_device, IsPaired()).WillByDefault(Return(true));
    notification_controller->DeviceChanged(mock_adapter_.get(), mock_device);
  }

  void SimulateDeviceBonded(
      BluetoothNotificationController* notification_controller,
      device::MockBluetoothDevice* mock_device) {
    ON_CALL(*mock_device, IsBonded()).WillByDefault(Return(true));
    notification_controller->DeviceChanged(mock_adapter_.get(), mock_device);
  }

  ToastOverlay* GetCurrentOverlay() {
    return toast_manager_->GetCurrentOverlayForTesting();
  }

  TestMessageCenter test_message_center_;
  scoped_refptr<device::MockBluetoothAdapter> mock_adapter_;
  std::unique_ptr<BluetoothNotificationController> notification_controller_;
  raw_ptr<TestSystemTrayClient, DanglingUntriaged> system_tray_client_;
  std::unique_ptr<device::MockBluetoothDevice> bluetooth_device_1_;
  std::unique_ptr<device::MockBluetoothDevice> bluetooth_device_2_;
  raw_ptr<ToastManagerImpl, DanglingUntriaged> toast_manager_ = nullptr;
};

TEST_F(BluetoothNotificationControllerTest, DiscoverableToast) {
  VerifyDiscoverableToastVisibility(/*visible=*/false);

  GetSessionControllerClient()->SetSessionState(
      session_manager::SessionState::LOCKED);

  ShowDiscoverableToast(notification_controller_.get());

  VerifyDiscoverableToastVisibility(/*visible=*/false);

  GetSessionControllerClient()->SetSessionState(
      session_manager::SessionState::ACTIVE);

  ShowDiscoverableToast(notification_controller_.get());

  VerifyDiscoverableToastVisibility(/*visible=*/true);
}

TEST_F(BluetoothNotificationControllerTest,
       DiscoverableToast_NearbyShareEnableHighVisibilityRequestActive) {
  VerifyDiscoverableToastVisibility(/*visible=*/false);

  auto* nearby_share_delegate_ = static_cast<TestNearbyShareDelegate*>(
      Shell::Get()->nearby_share_delegate());
  nearby_share_delegate_->set_is_enable_high_visibility_request_active(true);

  ShowDiscoverableToast(notification_controller_.get());

  VerifyDiscoverableToastVisibility(/*visible=*/false);
}

TEST_F(BluetoothNotificationControllerTest,
       DiscoverableToast_NearbyShareHighVisibilityOn) {
  VerifyDiscoverableToastVisibility(/*visible=*/false);

  auto* nearby_share_delegate_ = static_cast<TestNearbyShareDelegate*>(
      Shell::Get()->nearby_share_delegate());
  nearby_share_delegate_->set_is_high_visibility_on(true);

  ShowDiscoverableToast(notification_controller_.get());

  VerifyDiscoverableToastVisibility(/*visible=*/false);
}

TEST_F(BluetoothNotificationControllerTest, PairingNotification) {
  VerifyPairingNotificationVisibility(/*visible=*/false);

  ShowPairingNotification(notification_controller_.get(),
                          bluetooth_device_1_.get());
  VerifyPairingNotificationVisibility(/*visible=*/true);

  // Simulate the device being paired. This should not remove the pairing
  // notification.
  SimulateDevicePaired(notification_controller_.get(),
                       bluetooth_device_1_.get());
  VerifyPairingNotificationVisibility(/*visible=*/true);

  // Simulate the device being bonded. This should remove the pairing
  // notification.
  SimulateDeviceBonded(notification_controller_.get(),
                       bluetooth_device_1_.get());
  VerifyPairingNotificationVisibility(/*visible=*/false);
}

}  // namespace ash