0

Adds Bluetooth Unavailable State View to Bluetooth Detailed Tray View

This CL creates an unavailable state view for when the Bluetooth is
unavailable that is hidden behind a feature flag.

Bug: 357945367
Change-Id: I50df40ec4c5e121971f89b875963c075969866b7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5779783
Auto-Submit: Bhuvana Betini <bbetini@google.com>
Reviewed-by: Ahmed Mehfooz <amehfooz@chromium.org>
Reviewed-by: Andre Le <leandre@chromium.org>
Commit-Queue: Bhuvana Betini <bbetini@google.com>
Reviewed-by: Xiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1343127}
This commit is contained in:
Bhuvana Betini
2024-08-17 00:25:50 +00:00
committed by Chromium LUCI CQ
parent b9b5f34280
commit fd36ac82ee
9 changed files with 160 additions and 13 deletions

@ -666,6 +666,12 @@ Style notes:
<message name="IDS_ASH_STATUS_TRAY_BLUETOOTH_UNAVAILABLE_TOOLTIP" desc="The tooltip text to notify that bluetooth is unavailable.">
Bluetooth is unavailable
</message>
<message name="IDS_ASH_STATUS_TRAY_BLUETOOTH_UNAVAILABLE_STATE_TITLE" desc="The main label for the Bluetooth Quick Settings detailed view when Bluetooth is unavailable.">
Bluetooth unavailable
</message>
<message name="IDS_ASH_STATUS_TRAY_BLUETOOTH_UNAVAILABLE_STATE_SUBTITLE" desc="The secondary label for the Bluetooth Quick Settings detailed view when Bluetooth is unavailable.">
Try shutting down your device.
</message>
<message name="IDS_ASH_STATUS_TRAY_BLUETOOTH_MULTIPLE_DEVICES_CONNECTED_TOOLTIP_LEGACY" desc="The tooltip text to notify that multiple bluetooth devices are connected. [ICU Syntax]">
{NUM_DEVICES, plural,
=1 {Connected to a device}

@ -0,0 +1 @@
a81f43cb0e80e44ab788ce45bfa250cd4ca63524

@ -0,0 +1 @@
ff5af3b6e966fdbf562f366720876bc0858fe4fd

@ -70,6 +70,8 @@
<structure type="lottie" name="IDR_KEYBOARD_FN_KEY_NUDGE_IMAGE" file="unscaled_resources/keyboard_fn_key_nudge_image.json" compress="gzip" />
<!-- Capslock Nudge -->
<structure type="lottie" name="IDR_KEYBOARD_CAPSLOCK_KEY_NUDGE_IMAGE" file="unscaled_resources/keyboard_capslock_key_nudge_image.json" compress="gzip" />
<!-- System Tray Bluetooth Unavailable Images -->
<structure type="lottie" name="IDR_TRAY_BLUETOOTH_UNAVAILABLE_STATE_IMAGE" file="unscaled_resources/bluetooth_unavailable_state_image.json" compress="gzip" />
</structures>
</release>
</grit>

File diff suppressed because one or more lines are too long

@ -21,6 +21,7 @@
#include "build/chromeos_buildflags.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/services/bluetooth_config/public/cpp/cros_bluetooth_config_util.h"
#include "chromeos/constants/chromeos_features.h"
#include "mojo/public/cpp/bindings/clone_traits.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/view.h"
@ -84,7 +85,12 @@ std::u16string BluetoothDetailedViewController::GetAccessibleName() const {
void BluetoothDetailedViewController::OnPropertiesUpdated(
bluetooth_config::mojom::BluetoothSystemPropertiesPtr properties) {
if (properties->system_state == BluetoothSystemState::kUnavailable) {
// The tray controller should only be transitioning to the main view when this
// feature is disabled since the detailed tray view and the Bluetooth Pod in
// QS would be hidden. However, when the feature is enabled, the Bluetooth Pod
// is visible and the user should be able to see the detailed tray view.
if (!chromeos::features::IsBluetoothWifiQSPodRefreshEnabled() &&
properties->system_state == BluetoothSystemState::kUnavailable) {
tray_controller_->TransitionToMainView(
/*restore_focus=*/true); // Deletes |this|.
return;

@ -9,6 +9,7 @@
#include "ash/ash_element_identifiers.h"
#include "ash/bubble/bubble_utils.h"
#include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
#include "ash/public/cpp/system_tray_client.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h"
@ -68,6 +69,13 @@ BluetoothDetailedViewImpl::BluetoothDetailedViewImpl(
CreateScrollableList();
CreateTopContainer();
CreateMainContainer();
if (chromeos::features::IsBluetoothWifiQSPodRefreshEnabled()) {
CreateZeroStateView(std::make_unique<ZeroStateView>(
/*image_id=*/IDR_TRAY_BLUETOOTH_UNAVAILABLE_STATE_IMAGE,
/*title_id=*/IDS_ASH_STATUS_TRAY_BLUETOOTH_UNAVAILABLE_STATE_TITLE,
/*subtitle_id=*/
IDS_ASH_STATUS_TRAY_BLUETOOTH_UNAVAILABLE_STATE_SUBTITLE));
}
UpdateBluetoothEnabledState(BluetoothSystemState::kDisabled);
device::RecordUiSurfaceDisplayed(
device::BluetoothUiSurface::kBluetoothQuickSettings);
@ -81,6 +89,15 @@ views::View* BluetoothDetailedViewImpl::GetAsView() {
void BluetoothDetailedViewImpl::UpdateBluetoothEnabledState(
const BluetoothSystemState system_state) {
if (chromeos::features::IsBluetoothWifiQSPodRefreshEnabled() &&
system_state == BluetoothSystemState::kUnavailable) {
SetZeroStateViewVisibility(true);
return;
}
if (chromeos::features::IsBluetoothWifiQSPodRefreshEnabled()) {
SetZeroStateViewVisibility(false);
}
bool is_enabled_or_enabling = IsBluetoothEnabledOrEnabling(system_state);
// Use square corners on the bottom edge when Bluetooth is enabled.

@ -4,6 +4,8 @@
#include <string>
#include "ash/system/bluetooth/bluetooth_detailed_view_impl.h"
#include "ash/system/tray/fake_detailed_view_delegate.h"
#include "ash/system/tray/tray_detailed_view.h"
#include "ash/system/unified/quick_settings_view.h"
#include "ash/system/unified/unified_system_tray.h"
@ -13,15 +15,20 @@
#include "ash/test/ash_test_helper.h"
#include "ash/test/pixel/ash_pixel_differ.h"
#include "ash/test/pixel/ash_pixel_test_init_params.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/ash/services/bluetooth_config/fake_adapter_state_controller.h"
#include "chromeos/ash/services/bluetooth_config/fake_device_cache.h"
#include "chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom-shared.h"
#include "chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
#include "chromeos/ash/services/bluetooth_config/scoped_bluetooth_config_test_helper.h"
#include "chromeos/constants/chromeos_features.h"
namespace ash {
namespace {
using bluetooth_config::ScopedBluetoothConfigTestHelper;
using bluetooth_config::mojom::BluetoothDeviceProperties;
using bluetooth_config::mojom::BluetoothSystemState;
using bluetooth_config::mojom::DeviceConnectionState;
using bluetooth_config::mojom::PairedBluetoothDeviceProperties;
using bluetooth_config::mojom::PairedBluetoothDevicePropertiesPtr;
@ -38,10 +45,26 @@ PairedBluetoothDevicePropertiesPtr CreatePairedDevice(
return paired_properties;
}
// Returns appropriate screenshot suffix based on whether the feature flag is
// enabled.
std::string GetScreenshotName(const std::string& test_name, bool enabled) {
return test_name + (enabled ? "_unavailable_state_enabled"
: "_unavailable_state_disabled");
}
// Pixel tests for the quick settings Bluetooth detailed view.
class BluetoothDetailedViewImplPixelTest : public AshTestBase {
class BluetoothDetailedViewImplPixelTest
: public AshTestBase,
public testing::WithParamInterface<bool> {
public:
BluetoothDetailedViewImplPixelTest() = default;
BluetoothDetailedViewImplPixelTest() {
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list_->InitWithFeatureState(
chromeos::features::kBluetoothWifiQSPodRefresh,
IsBluetoothWifiQSPodRefreshEnabled());
}
bool IsBluetoothWifiQSPodRefreshEnabled() { return GetParam(); }
// AshTestBase:
std::optional<pixel_test::InitParams> CreatePixelTestInitParams()
@ -57,9 +80,25 @@ class BluetoothDetailedViewImplPixelTest : public AshTestBase {
->fake_device_cache()
->SetPairedDevices(std::move(paired_devices));
}
void SetBluetoothSystemState(BluetoothSystemState system_state) {
ash_test_helper()
->bluetooth_config_test_helper()
->fake_adapter_state_controller()
->SetSystemState(system_state);
base::RunLoop().RunUntilIdle();
}
private:
std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
};
TEST_F(BluetoothDetailedViewImplPixelTest, Basics) {
INSTANTIATE_TEST_SUITE_P(
All,
BluetoothDetailedViewImplPixelTest,
/*IsBluetoothWifiQSPodRefreshEnabled()=*/testing::Bool());
TEST_P(BluetoothDetailedViewImplPixelTest, Basics) {
// Create test devices.
std::vector<PairedBluetoothDevicePropertiesPtr> paired_devices;
paired_devices.push_back(
@ -85,9 +124,38 @@ TEST_F(BluetoothDetailedViewImplPixelTest, Basics) {
// Compare pixels.
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
"check_view",
GetScreenshotName("check_view", IsBluetoothWifiQSPodRefreshEnabled()),
/*revision_number=*/9, detailed_view));
}
TEST_P(BluetoothDetailedViewImplPixelTest, BluetoothUnavailable) {
if (!IsBluetoothWifiQSPodRefreshEnabled()) {
GTEST_SKIP() << "If Bluetooth is unavailable the BluetoothDetailedView is "
"not accessible";
}
SetBluetoothSystemState(BluetoothSystemState::kUnavailable);
// Show the system tray bubble.
UnifiedSystemTray* system_tray = GetPrimaryUnifiedSystemTray();
system_tray->ShowBubble();
ASSERT_TRUE(system_tray->bubble());
// Show the Bluetooth detailed view.
system_tray->bubble()
->unified_system_tray_controller()
->ShowBluetoothDetailedView();
TrayDetailedView* detailed_view =
system_tray->bubble()
->quick_settings_view()
->GetDetailedViewForTest<TrayDetailedView>();
ASSERT_TRUE(detailed_view);
// Compare pixels.
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
GetScreenshotName("bluetooth_unavailable_view",
IsBluetoothWifiQSPodRefreshEnabled()),
/*revision_number=*/0, detailed_view));
}
} // namespace
} // namespace ash

@ -14,8 +14,11 @@
#include "ash/system/tray/hover_highlight_view.h"
#include "ash/test/ash_test_base.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/constants/chromeos_features.h"
#include "mojo/public/cpp/bindings/clone_traits.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/widget/widget.h"
@ -57,8 +60,16 @@ class FakeBluetoothDetailedViewDelegate
} // namespace
class BluetoothDetailedViewImplTest : public AshTestBase {
class BluetoothDetailedViewImplTest : public AshTestBase,
public testing::WithParamInterface<bool> {
public:
BluetoothDetailedViewImplTest() {
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list_->InitWithFeatureState(
chromeos::features::kBluetoothWifiQSPodRefresh,
IsBluetoothWifiQSPodRefreshEnabled());
}
void SetUp() override {
AshTestBase::SetUp();
@ -81,6 +92,8 @@ class BluetoothDetailedViewImplTest : public AshTestBase {
return bluetooth_detailed_view_->settings_button_;
}
bool IsBluetoothWifiQSPodRefreshEnabled() { return GetParam(); }
HoverHighlightView* GetToggleRow() {
return bluetooth_detailed_view_->toggle_row_;
}
@ -100,9 +113,15 @@ class BluetoothDetailedViewImplTest : public AshTestBase {
FakeDetailedViewDelegate detailed_view_delegate_;
raw_ptr<BluetoothDetailedViewImpl, DanglingUntriaged>
bluetooth_detailed_view_ = nullptr;
std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
};
TEST_F(BluetoothDetailedViewImplTest, PressingSettingsButtonOpensSettings) {
INSTANTIATE_TEST_SUITE_P(
All,
BluetoothDetailedViewImplTest,
/*IsBluetoothWifiQSPodRefreshEnabled()=*/testing::Bool());
TEST_P(BluetoothDetailedViewImplTest, PressingSettingsButtonOpensSettings) {
views::Button* settings_button = GetSettingsButton();
// Clicking the button at the lock screen does nothing.
@ -120,7 +139,7 @@ TEST_F(BluetoothDetailedViewImplTest, PressingSettingsButtonOpensSettings) {
EXPECT_EQ(1u, detailed_view_delegate_.close_bubble_call_count());
}
TEST_F(BluetoothDetailedViewImplTest,
TEST_P(BluetoothDetailedViewImplTest,
UpdateBluetoothEnabledStateChangesUIState) {
HoverHighlightView* toggle_row = GetToggleRow();
Switch* toggle_button = GetToggleButton();
@ -138,6 +157,12 @@ TEST_F(BluetoothDetailedViewImplTest,
toggle_button->GetTooltipText());
EXPECT_TRUE(main_container->GetVisible());
EXPECT_TRUE(pair_new_device_view->GetVisible());
if (IsBluetoothWifiQSPodRefreshEnabled()) {
EXPECT_FALSE(
bluetooth_detailed_view_->zero_state_view_for_testing()->GetVisible());
}
EXPECT_TRUE(
bluetooth_detailed_view_->scroll_view_for_testing()->GetVisible());
bluetooth_detailed_view_->UpdateBluetoothEnabledState(
BluetoothSystemState::kDisabled);
@ -149,7 +174,12 @@ TEST_F(BluetoothDetailedViewImplTest,
EXPECT_EQ(u"Toggle Bluetooth. Bluetooth is off.",
toggle_button->GetTooltipText());
EXPECT_FALSE(main_container->GetVisible());
if (IsBluetoothWifiQSPodRefreshEnabled()) {
EXPECT_FALSE(
bluetooth_detailed_view_->zero_state_view_for_testing()->GetVisible());
}
EXPECT_TRUE(
bluetooth_detailed_view_->scroll_view_for_testing()->GetVisible());
bluetooth_detailed_view_->UpdateBluetoothEnabledState(
BluetoothSystemState::kEnabling);
EXPECT_EQ(u"On", toggle_row->text_label()->GetText());
@ -160,9 +190,24 @@ TEST_F(BluetoothDetailedViewImplTest,
toggle_button->GetTooltipText());
EXPECT_TRUE(main_container->GetVisible());
EXPECT_FALSE(pair_new_device_view->GetVisible());
if (IsBluetoothWifiQSPodRefreshEnabled()) {
EXPECT_FALSE(
bluetooth_detailed_view_->zero_state_view_for_testing()->GetVisible());
}
EXPECT_TRUE(
bluetooth_detailed_view_->scroll_view_for_testing()->GetVisible());
bluetooth_detailed_view_->UpdateBluetoothEnabledState(
BluetoothSystemState::kUnavailable);
if (IsBluetoothWifiQSPodRefreshEnabled()) {
EXPECT_TRUE(
bluetooth_detailed_view_->zero_state_view_for_testing()->GetVisible());
EXPECT_FALSE(
bluetooth_detailed_view_->scroll_view_for_testing()->GetVisible());
}
}
TEST_F(BluetoothDetailedViewImplTest, PressingToggleRowNotifiesDelegate) {
TEST_P(BluetoothDetailedViewImplTest, PressingToggleRowNotifiesDelegate) {
HoverHighlightView* toggle_row = GetToggleRow();
EXPECT_FALSE(bluetooth_detailed_view_delegate_.last_toggle_state_);
@ -171,7 +216,7 @@ TEST_F(BluetoothDetailedViewImplTest, PressingToggleRowNotifiesDelegate) {
EXPECT_TRUE(bluetooth_detailed_view_delegate_.last_toggle_state_);
}
TEST_F(BluetoothDetailedViewImplTest, PressingToggleButtonNotifiesDelegate) {
TEST_P(BluetoothDetailedViewImplTest, PressingToggleButtonNotifiesDelegate) {
Switch* toggle_button = GetToggleButton();
views::Button* pair_new_device_view = GetPairNewDeviceView();
@ -186,7 +231,7 @@ TEST_F(BluetoothDetailedViewImplTest, PressingToggleButtonNotifiesDelegate) {
EXPECT_FALSE(pair_new_device_view->GetVisible());
}
TEST_F(BluetoothDetailedViewImplTest, PressingPairNewDeviceNotifiesDelegate) {
TEST_P(BluetoothDetailedViewImplTest, PressingPairNewDeviceNotifiesDelegate) {
bluetooth_detailed_view_->UpdateBluetoothEnabledState(
BluetoothSystemState::kEnabled);
views::test::RunScheduledLayout(bluetooth_detailed_view_);
@ -198,7 +243,7 @@ TEST_F(BluetoothDetailedViewImplTest, PressingPairNewDeviceNotifiesDelegate) {
bluetooth_detailed_view_delegate_.pair_new_device_requested_count_);
}
TEST_F(BluetoothDetailedViewImplTest, SelectingDeviceListItemNotifiesDelegate) {
TEST_P(BluetoothDetailedViewImplTest, SelectingDeviceListItemNotifiesDelegate) {
bluetooth_detailed_view_->UpdateBluetoothEnabledState(
BluetoothSystemState::kEnabled);