// Copyright 2021 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_device_list_controller_impl.h"
#include <memory>
#include <string>
#include <vector>
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/bluetooth/bluetooth_detailed_view.h"
#include "ash/system/bluetooth/bluetooth_device_list_item_view.h"
#include "ash/system/bluetooth/fake_bluetooth_detailed_view.h"
#include "ash/system/tray/tri_view.h"
#include "ash/test/ash_test_base.h"
#include "chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/controls/label.h"
namespace ash {
namespace {
using bluetooth_config::mojom::BluetoothDeviceProperties;
using bluetooth_config::mojom::PairedBluetoothDeviceProperties;
using bluetooth_config::mojom::PairedBluetoothDevicePropertiesPtr;
const char kDeviceId1[] = "/device/id/1";
const char kDeviceId2[] = "/device/id/2";
const char kDeviceNickname[] = "mau5";
} // namespace
class BluetoothDeviceListControllerTest : public AshTestBase {
public:
void SetUp() override {
AshTestBase::SetUp();
fake_bluetooth_detailed_view_ =
std::make_unique<FakeBluetoothDetailedView>(/*delegate=*/nullptr);
bluetooth_device_list_controller_impl_ =
std::make_unique<BluetoothDeviceListControllerImpl>(
fake_bluetooth_detailed_view_.get());
}
void TearDown() override { AshTestBase::TearDown(); }
const TriView* FindConnectedSubHeader() {
return FindSubHeaderWithText(l10n_util::GetStringUTF16(
IDS_ASH_STATUS_TRAY_BLUETOOTH_CURRENTLY_CONNECTED_DEVICES));
}
const TriView* FindPreviouslyConnectedSubHeader() {
return FindSubHeaderWithText(l10n_util::GetStringUTF16(
IDS_ASH_STATUS_TRAY_BLUETOOTH_PREVIOUSLY_CONNECTED_DEVICES));
}
const TriView* FindNoDeviceConnectedSubHeader() {
return FindSubHeaderWithText(l10n_util::GetStringUTF16(
IDS_ASH_STATUS_TRAY_BLUETOOTH_NO_DEVICE_CONNECTED));
}
PairedBluetoothDevicePropertiesPtr BuildDeviceProperties(
const std::string& id) {
PairedBluetoothDevicePropertiesPtr device_properties =
PairedBluetoothDeviceProperties::New();
device_properties->device_properties = BluetoothDeviceProperties::New();
device_properties->device_properties->id = id;
return device_properties;
}
const std::u16string& GetSubHeaderText(const TriView* sub_header) {
EXPECT_TRUE(sub_header);
EXPECT_EQ(1u, sub_header->children().at(1)->children().size());
return static_cast<views::Label*>(
sub_header->children().at(1)->children().at(0))
->GetText();
}
const char* GetDeviceId(const BluetoothDeviceListItemView* device_item_view) {
return device_item_view->device_properties()->device_properties->id.c_str();
}
const BluetoothDeviceListItemView* GetFirstDeviceView() {
EXPECT_LT(1u, device_list()->children().size());
return static_cast<BluetoothDeviceListItemView*>(
device_list()->children().at(1));
}
void CheckDeviceListOrdering(size_t connected_device_count,
size_t previously_connected_device_count) {
if (connected_device_count && previously_connected_device_count) {
const TriView* connected_sub_header = FindConnectedSubHeader();
const TriView* previously_connected_sub_header =
FindPreviouslyConnectedSubHeader();
EXPECT_TRUE(connected_sub_header);
EXPECT_TRUE(previously_connected_sub_header);
const size_t connected_index =
device_list()->GetIndexOf(connected_sub_header).value();
EXPECT_EQ(0u, connected_index);
return;
}
if (connected_device_count) {
const TriView* connected_sub_header = FindConnectedSubHeader();
EXPECT_TRUE(connected_sub_header);
EXPECT_EQ(0u, device_list()->GetIndexOf(connected_sub_header));
EXPECT_EQ(connected_device_count + 1, device_list()->children().size());
return;
}
if (previously_connected_device_count) {
const TriView* previously_connected_sub_header =
FindPreviouslyConnectedSubHeader();
EXPECT_TRUE(previously_connected_sub_header);
EXPECT_EQ(0u, device_list()->GetIndexOf(previously_connected_sub_header));
EXPECT_EQ(previously_connected_device_count + 1,
device_list()->children().size());
return;
}
const TriView* no_device_connected_sub_header =
FindNoDeviceConnectedSubHeader();
EXPECT_TRUE(no_device_connected_sub_header);
EXPECT_EQ(0u, device_list()->GetIndexOf(no_device_connected_sub_header));
EXPECT_EQ(1u, device_list()->children().size());
}
void CheckNotifyDeviceListChangedCount(size_t call_count) {
EXPECT_EQ(call_count, fake_bluetooth_detailed_view()
->notify_device_list_changed_call_count());
}
views::View* device_list() {
return static_cast<BluetoothDetailedView*>(
fake_bluetooth_detailed_view_.get())
->device_list();
}
BluetoothDeviceListController* bluetooth_device_list_controller() {
return bluetooth_device_list_controller_impl_.get();
}
FakeBluetoothDetailedView* fake_bluetooth_detailed_view() {
return fake_bluetooth_detailed_view_.get();
}
protected:
const std::vector<PairedBluetoothDevicePropertiesPtr> empty_list_;
private:
const TriView* FindSubHeaderWithText(const std::u16string text) {
for (const views::View* view : device_list()->children()) {
if (std::strcmp("TriView", view->GetClassName()))
continue;
const TriView* sub_header = static_cast<const TriView*>(view);
if (GetSubHeaderText(sub_header) == text)
return sub_header;
}
return nullptr;
}
std::unique_ptr<FakeBluetoothDetailedView> fake_bluetooth_detailed_view_;
std::unique_ptr<BluetoothDeviceListControllerImpl>
bluetooth_device_list_controller_impl_;
};
TEST_F(BluetoothDeviceListControllerTest,
HasCorrectSubHeaderWithNoPairedDevices) {
CheckNotifyDeviceListChangedCount(/*call_count=*/0u);
bluetooth_device_list_controller()->UpdateBluetoothEnabledState(true);
bluetooth_device_list_controller()->UpdateDeviceList(
/*connected=*/empty_list_,
/*previously_connected=*/empty_list_);
CheckNotifyDeviceListChangedCount(/*call_count=*/1u);
EXPECT_EQ(1u, device_list()->children().size());
const TriView* no_device_connected_sub_header =
FindNoDeviceConnectedSubHeader();
EXPECT_TRUE(no_device_connected_sub_header);
}
TEST_F(BluetoothDeviceListControllerTest,
HasCorrectDeviceListOrderWithPairedAndPreviouslyPairedDevices) {
CheckNotifyDeviceListChangedCount(/*call_count=*/0u);
bluetooth_device_list_controller()->UpdateBluetoothEnabledState(true);
std::vector<PairedBluetoothDevicePropertiesPtr> connected_list;
connected_list.push_back(BuildDeviceProperties(kDeviceId1));
bluetooth_device_list_controller()->UpdateDeviceList(
/*connected=*/connected_list,
/*previously_connected=*/empty_list_);
CheckNotifyDeviceListChangedCount(/*call_count=*/1u);
const TriView* connected_devices_sub_header = FindConnectedSubHeader();
EXPECT_EQ(2u, device_list()->children().size());
EXPECT_STREQ(kDeviceId1, GetDeviceId(GetFirstDeviceView()));
EXPECT_TRUE(connected_devices_sub_header);
CheckDeviceListOrdering(
/*connected_device_count=*/connected_list.size(),
/*previously_connected_device_count=*/empty_list_.size());
std::vector<PairedBluetoothDevicePropertiesPtr> previously_connected_list;
previously_connected_list.push_back(BuildDeviceProperties(kDeviceId2));
bluetooth_device_list_controller()->UpdateDeviceList(
/*connected=*/empty_list_,
/*previously_connected=*/previously_connected_list);
CheckNotifyDeviceListChangedCount(/*call_count=*/2u);
const TriView* previously_connected_devices_sub_header =
FindPreviouslyConnectedSubHeader();
EXPECT_EQ(2u, device_list()->children().size());
EXPECT_STREQ(kDeviceId2, GetDeviceId(GetFirstDeviceView()));
EXPECT_TRUE(previously_connected_devices_sub_header);
CheckDeviceListOrdering(
/*connected_device_count=*/0,
/*previously_connected_device_count=*/previously_connected_list.size());
// "Update" the device list multiple times to be sure that no children are
// duplicated and every child is re-ordered correctly.
for (int i = 0; i < 2; i++) {
bluetooth_device_list_controller()->UpdateDeviceList(
/*connected=*/connected_list,
/*previously_connected=*/previously_connected_list);
}
CheckNotifyDeviceListChangedCount(/*call_count=*/4u);
// This is confusing but `device_list()` is actually the scroll contents of
// the bluetooth detailed view, rather than a list of devices. So the count
// here is a combination of the following views (all are optional):
// * Connected device header
// * Connected device list
// * Previously connected device header
// * Previously connected device list
const int connected_header_count = FindConnectedSubHeader() ? 1 : 0;
const int previously_connected_header_count =
FindPreviouslyConnectedSubHeader() ? 1 : 0;
const size_t device_count =
connected_list.size() + previously_connected_list.size();
const auto expected_device_list_size =
connected_header_count + previously_connected_header_count + device_count;
EXPECT_EQ(expected_device_list_size, device_list()->children().size());
CheckDeviceListOrdering(
/*connected_device_count=*/connected_list.size(),
/*previously_connected_device_count=*/previously_connected_list.size());
}
TEST_F(BluetoothDeviceListControllerTest, ExistingDeviceViewsAreUpdated) {
CheckNotifyDeviceListChangedCount(/*call_count=*/0u);
bluetooth_device_list_controller()->UpdateBluetoothEnabledState(true);
std::vector<PairedBluetoothDevicePropertiesPtr> connected_list;
connected_list.push_back(BuildDeviceProperties(kDeviceId1));
bluetooth_device_list_controller()->UpdateDeviceList(
/*connected=*/connected_list,
/*previously_connected=*/empty_list_);
CheckNotifyDeviceListChangedCount(/*call_count=*/1u);
EXPECT_EQ(2u, device_list()->children().size());
const BluetoothDeviceListItemView* first_item = GetFirstDeviceView();
EXPECT_FALSE(first_item->device_properties()->nickname.has_value());
connected_list.at(0)->nickname = kDeviceNickname;
bluetooth_device_list_controller()->UpdateDeviceList(
/*connected=*/connected_list,
/*previously_connected=*/empty_list_);
CheckNotifyDeviceListChangedCount(/*call_count=*/2u);
EXPECT_EQ(2u, device_list()->children().size());
EXPECT_EQ(1u, device_list()->GetIndexOf(first_item));
EXPECT_TRUE(first_item->device_properties()->nickname.has_value());
EXPECT_STREQ(kDeviceNickname,
first_item->device_properties()->nickname.value().c_str());
}
TEST_F(BluetoothDeviceListControllerTest,
DeviceListIsClearedWhenBluetoothBecomesDisabled) {
CheckNotifyDeviceListChangedCount(/*call_count=*/0u);
bluetooth_device_list_controller()->UpdateBluetoothEnabledState(true);
std::vector<PairedBluetoothDevicePropertiesPtr> connected_list;
connected_list.push_back(BuildDeviceProperties(kDeviceId1));
bluetooth_device_list_controller()->UpdateDeviceList(
/*connected=*/connected_list,
/*previously_connected=*/empty_list_);
CheckNotifyDeviceListChangedCount(/*call_count=*/1u);
EXPECT_EQ(2u, device_list()->children().size());
bluetooth_device_list_controller()->UpdateBluetoothEnabledState(false);
EXPECT_EQ(0u, device_list()->children().size());
}
} // namespace ash