0

Notify bluetooth device changes on device added

The BluetoothDevicesObserver notifies its observers when a connected
device's configuration changed or when the adapter is powered on/off.
This misses the case when a device is added to the adapter, causing
folded back ChromeOS devices to boot into tablet mode despite having a
bluetooth mouse connected.

Added unit tests for TabletModeController to make sure the observer
call correctly trigger leaving tablet mode and starting in laptop mode
when folded back.

Bug: b:362082543
Change-Id: I65356244a1d69dde2391668a7ffce3936bf2c150
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5980092
Reviewed-by: Mitsuru Oshima <oshima@chromium.org>
Commit-Queue: Vincent Chiang <vincentchiang@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1379798}
This commit is contained in:
Vincent Chiang
2024-11-07 18:09:31 +00:00
committed by Chromium LUCI CQ
parent eb8b8cea2e
commit 22c4bacc08
6 changed files with 111 additions and 1 deletions

@ -5482,6 +5482,7 @@ static_library("test_support") {
"//components/user_manager:user_manager",
"//components/viz/test:test_support",
"//device/bluetooth",
"//device/bluetooth:mocks",
"//google_apis/common:test_support",
"//services/device/public/cpp:test_support",
"//services/device/public/mojom",

@ -38,6 +38,11 @@ void BluetoothDevicesObserver::AdapterPoweredChanged(
adapter_or_device_changed_callback_.Run(/*device=*/nullptr);
}
void BluetoothDevicesObserver::DeviceAdded(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) {
adapter_or_device_changed_callback_.Run(device);
}
void BluetoothDevicesObserver::DeviceChanged(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) {
adapter_or_device_changed_callback_.Run(device);

@ -40,6 +40,8 @@ class ASH_EXPORT BluetoothDevicesObserver
bool present) override;
void AdapterPoweredChanged(device::BluetoothAdapter* adapter,
bool powered) override;
void DeviceAdded(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) override;
void DeviceChanged(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) override;

@ -4,12 +4,16 @@
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include <vector>
#include "ash/constants/ash_switches.h"
#include "ash/shell.h"
#include "base/command_line.h"
#include "base/numerics/math_constants.h"
#include "base/run_loop.h"
#include "base/time/default_tick_clock.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "ui/events/devices/device_data_manager_test_api.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/devices/touchpad_device.h"
@ -18,6 +22,8 @@ namespace ash {
namespace {
constexpr char kBluetoothDevicePublicAddress[] = "01:23:45:67:89:AB";
bool IsTabletModeControllerInitialized() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAshEnableTabletMode);
@ -66,6 +72,42 @@ void TabletModeControllerTestApi::AttachExternalTouchpad() {
}
}
void TabletModeControllerTestApi::AttachBluetoothMouse(
device::MockBluetoothAdapter* bluetooth_adapter) {
uint32_t test_vendor_id = 0x1111;
uint32_t test_product_id = 0x1112;
const char test_device_name[] = "bluetooth mouse";
// Need to set device to DeviceDataManager since that's the source of device
// look up. This will not trigger tablet mode since it is not yet connected to
// bt adapter.
ui::DeviceDataManagerTestApi().SetMouseDevices({ui::InputDevice(
5, ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH, test_device_name, "",
base::FilePath(), test_vendor_id, test_product_id, 0)});
std::unique_ptr<device::MockBluetoothDevice> mock_device =
std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>(
bluetooth_adapter, /*bluetooth_class=*/0, test_device_name,
kBluetoothDevicePublicAddress,
/*initially_paired=*/true, /*connected=*/true);
ON_CALL(*mock_device, GetDeviceType)
.WillByDefault(testing::Return(device::BluetoothDeviceType::MOUSE));
ON_CALL(*mock_device, GetVendorID)
.WillByDefault(testing::Return(test_vendor_id));
ON_CALL(*mock_device, GetProductID)
.WillByDefault(testing::Return(test_product_id));
std::vector<raw_ptr<const device::BluetoothDevice, VectorExperimental>>
devices;
devices.push_back(mock_device.get());
ON_CALL(*bluetooth_adapter, GetDevices)
.WillByDefault(testing::Return(devices));
for (auto& observer : bluetooth_adapter->GetObservers()) {
observer.DeviceAdded(bluetooth_adapter, mock_device.get());
}
}
void TabletModeControllerTestApi::DetachAllMice() {
// See comment in |AttachExternalMouse| for why we need to call
// |base::RunLoop::RunUntilIdle|.

@ -10,6 +10,7 @@
#include "ash/wm/tablet_mode/internal_input_devices_event_blocker.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/memory/raw_ptr.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
namespace ash {
@ -38,6 +39,7 @@ class TabletModeControllerTestApi {
// Called to attach an external mouse/touchpad. If we're currently in tablet
// mode, tablet mode will be ended because of this.
void AttachExternalMouse();
void AttachBluetoothMouse(device::MockBluetoothAdapter* bluetooth_adapter);
void AttachExternalTouchpad();
// Called in association with the above to remove all mice/touchpads.

@ -49,6 +49,7 @@
#include "base/test/simple_test_tick_clock.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/ui/frame/caption_buttons/snap_controller.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/test/test_window_delegate.h"
#include "ui/base/hit_test.h"
@ -127,6 +128,14 @@ class TabletModeControllerTest : public AshTestBase {
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kAshEnableTabletMode);
bluetooth_adapter_ =
base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
device::BluetoothAdapterFactory::SetAdapterForTesting(bluetooth_adapter_);
ON_CALL(*bluetooth_adapter_, IsPowered)
.WillByDefault(testing::Return(true));
ON_CALL(*bluetooth_adapter_, IsPresent)
.WillByDefault(testing::Return(true));
AshTestBase::SetUp();
AccelerometerReader::GetInstance()->RemoveObserver(
tablet_mode_controller());
@ -162,7 +171,13 @@ class TabletModeControllerTest : public AshTestBase {
}
void AttachExternalMouse() { test_api_->AttachExternalMouse(); }
void AttachBluetoothMouse() {
test_api_->AttachBluetoothMouse(bluetooth_adapter_.get());
}
void AttachExternalTouchpad() { test_api_->AttachExternalTouchpad(); }
void ClearBluetoothAdapter() {
testing::Mock::VerifyAndClear(bluetooth_adapter_.get());
}
void DetachAllMice() { test_api_->DetachAllMice(); }
void DetachAllTouchpads() { test_api_->DetachAllTouchpads(); }
@ -251,6 +266,9 @@ class TabletModeControllerTest : public AshTestBase {
base::SimpleTestTickClock test_tick_clock_;
scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>>
bluetooth_adapter_;
// Tracks user action counts.
base::UserActionTester user_action_tester_;
};
@ -870,7 +888,7 @@ TEST_F(TabletModeControllerTest, CannotEnterTabletModeWithExternalMouse) {
EXPECT_FALSE(display::Screen::GetScreen()->InTabletMode());
}
// Tests that when we plug in a external mouse the device will
// Tests that when we plug in an external mouse the device will
// leave tablet mode.
TEST_F(TabletModeControllerTest, LeaveTabletModeWhenExternalMouseConnected) {
// Start in tablet mode.
@ -890,6 +908,46 @@ TEST_F(TabletModeControllerTest, LeaveTabletModeWhenExternalMouseConnected) {
EXPECT_TRUE(AreEventsBlocked());
}
// Tests that when we connect a bluetooth mouse the device will
// leave tablet mode.
TEST_F(TabletModeControllerTest, LeaveTabletModeWhenBluetoothMouseConnected) {
// Start in tablet mode.
OpenLidToAngle(300.0f);
EXPECT_TRUE(display::Screen::GetScreen()->InTabletMode());
EXPECT_TRUE(AreEventsBlocked());
// Connect bluetooth mouse and verify that tablet mode has ended, but events
// are still blocked because the keyboard is still facing the bottom.
AttachBluetoothMouse();
EXPECT_FALSE(display::Screen::GetScreen()->InTabletMode());
EXPECT_TRUE(AreEventsBlocked());
// Verify that after disconnecting mouse, tablet mode will resume.
ClearBluetoothAdapter();
DetachAllMice();
EXPECT_TRUE(display::Screen::GetScreen()->InTabletMode());
EXPECT_TRUE(AreEventsBlocked());
}
// Tests that when bluetooth mouse is connected device will not start
// in tablet mode.
TEST_F(TabletModeControllerTest, StartInLaptopModeWhenBluetoothMouseConnected) {
// Have a bluetooth mouse connected in the beginning.
AttachBluetoothMouse();
// Start with device folded back and verify that it is not in tablet mode and
// events are blocked
OpenLidToAngle(300.0f);
EXPECT_FALSE(display::Screen::GetScreen()->InTabletMode());
EXPECT_TRUE(AreEventsBlocked());
// Verify that after unplugging the mouse, tablet mode will resume.
ClearBluetoothAdapter();
DetachAllMice();
EXPECT_TRUE(display::Screen::GetScreen()->InTabletMode());
EXPECT_TRUE(AreEventsBlocked());
}
// Test that plug in or out a mouse in laptop mode will not change current
// laptop mode.
TEST_F(TabletModeControllerTest, ExternalMouseInLaptopMode) {