blob: cd02d81dfa16f64019bc4cd99ad44e979735ff50 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <utility>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/accessibility/drag_event_rewriter.h"
#include "ash/accessibility/ui/accessibility_confirmation_dialog.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/shell.h"
#include "ash/system/accessibility/accessibility_feature_disable_dialog.h"
#include "base/metrics/statistics_recorder.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ash/accessibility/accessibility_feature_browsertest.h"
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "chrome/browser/ash/accessibility/accessibility_test_utils.h"
#include "chrome/browser/ash/accessibility/facegaze_bubble_test_helper.h"
#include "chrome/browser/ash/accessibility/facegaze_test_utils.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/browser_test.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_handler.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point.h"
namespace ash {
using Config = FaceGazeTestUtils::Config;
using FaceGazeGesture = FaceGazeTestUtils::FaceGazeGesture;
using MacroName = FaceGazeTestUtils::MacroName;
using MediapipeGesture = FaceGazeTestUtils::MediapipeGesture;
using MockFaceLandmarkerResult = FaceGazeTestUtils::MockFaceLandmarkerResult;
namespace {
// Default forehead location.
constexpr std::pair<double, double> kDefaultForeheadLocation =
std::make_pair(0.11, 0.21);
// Expected cursor location when the above forehead location is used.
constexpr gfx::Point kDefaultCursorLocation = gfx::Point(360, 560);
// The center of the screen.
constexpr gfx::Point kCenter = gfx::Point(600, 400);
PrefService* GetPrefs() {
return AccessibilityManager::Get()->profile()->GetPrefs();
}
aura::Window* GetRootWindow() {
auto* root_window = Shell::GetRootWindowForNewWindows();
if (!root_window) {
root_window = Shell::GetPrimaryRootWindow();
}
return root_window;
}
// A class that records mouse and key events.
class MockEventHandler : public ui::EventHandler {
public:
MockEventHandler() = default;
~MockEventHandler() override = default;
MockEventHandler(const MockEventHandler&) = delete;
MockEventHandler& operator=(const MockEventHandler&) = delete;
void OnKeyEvent(ui::KeyEvent* event) override {
key_events_.push_back(*event);
}
void OnMouseEvent(ui::MouseEvent* event) override {
if (event->source_device_id() != ui::EventDeviceId::ED_UNKNOWN_DEVICE) {
return;
}
ui::EventType type = event->type();
if (type == ui::EventType::kMousePressed ||
type == ui::EventType::kMouseReleased ||
type == ui::EventType::kMouseMoved ||
type == ui::EventType::kMousewheel ||
type == ui::EventType::kMouseDragged) {
mouse_events_.push_back(*event);
}
}
void ClearEvents() {
key_events_.clear();
mouse_events_.clear();
}
const std::vector<ui::KeyEvent>& key_events() const { return key_events_; }
const std::vector<ui::MouseEvent>& mouse_events() const {
return mouse_events_;
}
std::vector<ui::MouseEvent> mouse_events(ui::EventType type) const {
std::vector<ui::MouseEvent> events;
for (const auto& event : mouse_events_) {
if (event.type() == type) {
events.push_back(event);
}
}
return events;
}
private:
std::vector<ui::KeyEvent> key_events_;
std::vector<ui::MouseEvent> mouse_events_;
};
} // namespace
class FaceGazeIntegrationTest : public AccessibilityFeatureBrowserTest {
public:
FaceGazeIntegrationTest() = default;
~FaceGazeIntegrationTest() override = default;
FaceGazeIntegrationTest(const FaceGazeIntegrationTest&) = delete;
FaceGazeIntegrationTest& operator=(const FaceGazeIntegrationTest&) = delete;
protected:
// InProcessBrowserTest:
void SetUpCommandLine(base::CommandLine* command_line) override {
scoped_feature_list_.InitWithFeatureStates(
{{::features::kAccessibilityManifestV3AccessibilityCommon, true}});
AccessibilityFeatureBrowserTest::SetUpCommandLine(command_line);
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
utils_ = std::make_unique<FaceGazeTestUtils>();
bubble_helper_ = std::make_unique<FaceGazeBubbleTestHelper>();
GetRootWindow()->AddPreTargetHandler(&event_handler_);
}
void TearDownOnMainThread() override {
GetRootWindow()->RemovePreTargetHandler(&event_handler_);
InProcessBrowserTest::TearDownOnMainThread();
}
void AssertLatestMouseEvent(size_t num_events,
ui::EventType type,
const gfx::Point& root_location) {
std::vector<ui::MouseEvent> mouse_events = event_handler().mouse_events();
ASSERT_GT(mouse_events.size(), 0u);
ASSERT_EQ(num_events, mouse_events.size());
ASSERT_EQ(type, mouse_events[0].type());
ASSERT_EQ(root_location, mouse_events[0].root_location());
}
MockEventHandler& event_handler() { return event_handler_; }
FaceGazeTestUtils* utils() { return utils_.get(); }
FaceGazeBubbleTestHelper* bubble_helper() { return bubble_helper_.get(); }
private:
std::unique_ptr<FaceGazeTestUtils> utils_;
std::unique_ptr<FaceGazeBubbleTestHelper> bubble_helper_;
MockEventHandler event_handler_;
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, UpdateCursorLocation) {
utils()->EnableFaceGaze(Config().Default());
event_handler().ClearEvents();
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult().WithNormalizedForeheadLocation(
kDefaultForeheadLocation));
utils()->TriggerMouseControllerInterval();
utils()->AssertCursorAt(kDefaultCursorLocation);
// We expect two mouse move events to be received because the FaceGaze
// extension calls two APIs to update the cursor position.
// EventGenerator generates a non-synthetic event. 'setCursorPosition'
// generates a synthetic event.
const std::vector<ui::MouseEvent> mouse_events =
event_handler().mouse_events();
ASSERT_EQ(2u, mouse_events.size());
ASSERT_EQ(ui::EventType::kMouseMoved, mouse_events[0].type());
ASSERT_EQ(kDefaultCursorLocation, mouse_events[0].root_location());
ASSERT_FALSE(mouse_events[0].IsSynthesized());
ASSERT_EQ(ui::EventType::kMouseMoved, mouse_events[1].type());
ASSERT_EQ(kDefaultCursorLocation, mouse_events[1].root_location());
ASSERT_TRUE(mouse_events[1].IsSynthesized());
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, ResetCursor) {
const base::flat_map<FaceGazeGesture, MacroName> gestures_to_macros = {
{FaceGazeGesture::JAW_OPEN, MacroName::RESET_CURSOR}};
const base::flat_map<FaceGazeGesture, int> gestures_to_confidences = {
{FaceGazeGesture::JAW_OPEN, 70}};
utils()->EnableFaceGaze(Config().Default().WithBindings(
gestures_to_macros, gestures_to_confidences));
// Move cursor.
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult().WithNormalizedForeheadLocation(
kDefaultForeheadLocation));
utils()->TriggerMouseControllerInterval();
utils()->AssertCursorAt(kDefaultCursorLocation);
event_handler().ClearEvents();
// Reset the cursor to the center of the screen using a gesture.
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult().WithGesture(MediapipeGesture::JAW_OPEN, 90));
utils()->AssertCursorAt(kCenter);
// We expect one mouse move event to be received because the FaceGaze
// extension only calls one API to reset the cursor position, which sends a
// synthesized event.
const std::vector<ui::MouseEvent> mouse_events =
event_handler().mouse_events();
ASSERT_EQ(1u, mouse_events.size());
ASSERT_EQ(ui::EventType::kMouseMoved, mouse_events[0].type());
ASSERT_EQ(kCenter, mouse_events[0].root_location());
ASSERT_TRUE(mouse_events[0].IsSynthesized());
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest,
IgnoreGesturesWithLowConfidence) {
const base::flat_map<FaceGazeGesture, MacroName> gestures_to_macros = {
{FaceGazeGesture::JAW_OPEN, MacroName::RESET_CURSOR}};
const base::flat_map<FaceGazeGesture, int> gestures_to_confidences = {
{FaceGazeGesture::JAW_OPEN, 100}};
utils()->EnableFaceGaze(Config().Default().WithBindings(
gestures_to_macros, gestures_to_confidences));
// Move cursor.
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult().WithNormalizedForeheadLocation(
kDefaultForeheadLocation));
utils()->TriggerMouseControllerInterval();
utils()->AssertCursorAt(kDefaultCursorLocation);
// Attempt to reset the cursor to the center of the screen using a gesture.
// This gesture will be ignored because the gesture doesn't have high enough
// confidence.
event_handler().ClearEvents();
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult().WithGesture(MediapipeGesture::JAW_OPEN, 90));
utils()->AssertCursorAt(kDefaultCursorLocation);
ASSERT_EQ(0u, event_handler().mouse_events().size());
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest,
UpdateCursorLocationWithSpeed1) {
utils()->EnableFaceGaze(Config().Default().WithCursorSpeeds(
{/*up=*/1, /*down=*/1, /*left=*/1, /*right=*/1}));
// With cursor acceleration off and buffer size 1, one-pixel head movements
// correspond to one-pixel changes on screen.
double px = 1.0 / 1200;
double py = 1.0 / 800;
for (int i = 1; i < 10; ++i) {
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult().WithNormalizedForeheadLocation(
std::make_pair(0.1 + px * i, 0.2 + py * i)));
utils()->TriggerMouseControllerInterval();
utils()->AssertCursorAt(gfx::Point(600 - i, 400 + i));
}
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, SpaceKeyEvents) {
const base::flat_map<FaceGazeGesture, MacroName> gestures_to_macros = {
{FaceGazeGesture::MOUTH_LEFT, MacroName::KEY_PRESS_SPACE}};
const base::flat_map<FaceGazeGesture, int> gestures_to_confidences = {
{FaceGazeGesture::MOUTH_LEFT, 70}};
utils()->EnableFaceGaze(Config().Default().WithBindings(
gestures_to_macros, gestures_to_confidences));
// Open jaw for space key press.
event_handler().ClearEvents();
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult().WithGesture(MediapipeGesture::MOUTH_LEFT, 90));
ASSERT_EQ(0u, event_handler().mouse_events().size());
std::vector<ui::KeyEvent> key_events = event_handler().key_events();
ASSERT_EQ(1u, key_events.size());
ASSERT_EQ(ui::KeyboardCode::VKEY_SPACE, key_events[0].key_code());
ASSERT_EQ(ui::EventType::kKeyPressed, key_events[0].type());
// Release gesture for space key release.
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult().WithGesture(MediapipeGesture::MOUTH_LEFT, 10));
ASSERT_EQ(0u, event_handler().mouse_events().size());
key_events = event_handler().key_events();
ASSERT_EQ(2u, event_handler().key_events().size());
ASSERT_EQ(ui::KeyboardCode::VKEY_SPACE, key_events[1].key_code());
ASSERT_EQ(ui::EventType::kKeyReleased, key_events[1].type());
}
// The BrowsDown gesture is special because it is the combination of two
// separate facial gestures (BROW_DOWN_LEFT and BROW_DOWN_RIGHT). This test
// ensures that the associated action is performed if either of the gestures is
// detected.
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, BrowsDownGesture) {
const base::flat_map<FaceGazeGesture, MacroName> gestures_to_macros = {
{FaceGazeGesture::BROWS_DOWN, MacroName::RESET_CURSOR}};
const base::flat_map<FaceGazeGesture, int> gestures_to_confidences = {
{FaceGazeGesture::BROWS_DOWN, 40}};
utils()->EnableFaceGaze(
Config()
.Default()
.WithCursorLocation(gfx::Point(0, 0))
.WithBindings(gestures_to_macros, gestures_to_confidences)
.WithGestureRepeatDelayMs(0));
// If neither gesture is detected, then don't perform the associated action.
event_handler().ClearEvents();
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult()
.WithGesture(MediapipeGesture::BROW_DOWN_LEFT, 30)
.WithGesture(MediapipeGesture::BROW_DOWN_RIGHT, 30));
ASSERT_EQ(0u, event_handler().mouse_events().size());
// If BROW_DOWN_LEFT is recognized, then perform the action.
event_handler().ClearEvents();
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult()
.WithGesture(MediapipeGesture::BROW_DOWN_LEFT, 50)
.WithGesture(MediapipeGesture::BROW_DOWN_RIGHT, 30));
utils()->AssertCursorAt(kCenter);
AssertLatestMouseEvent(1, ui::EventType::kMouseMoved, kCenter);
// Reset the mouse cursor away from the center.
utils()->MoveMouseTo(gfx::Point(0, 0));
utils()->AssertCursorAt(gfx::Point(0, 0));
// If BROW_DOWN_RIGHT is recognized, then perform the action.
event_handler().ClearEvents();
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult()
.WithGesture(MediapipeGesture::BROW_DOWN_LEFT, 30)
.WithGesture(MediapipeGesture::BROW_DOWN_RIGHT, 50));
utils()->AssertCursorAt(kCenter);
AssertLatestMouseEvent(1, ui::EventType::kMouseMoved, kCenter);
// Reset the mouse cursor away from the center.
utils()->MoveMouseTo(gfx::Point(0, 0));
utils()->AssertCursorAt(gfx::Point(0, 0));
// If both of the gestures are recognized, then perform the action.
event_handler().ClearEvents();
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult()
.WithGesture(MediapipeGesture::BROW_DOWN_LEFT, 50)
.WithGesture(MediapipeGesture::BROW_DOWN_RIGHT, 50));
utils()->AssertCursorAt(kCenter);
AssertLatestMouseEvent(1, ui::EventType::kMouseMoved, kCenter);
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, MousePressAndReleaseEvents) {
const base::flat_map<FaceGazeGesture, MacroName> gestures_to_macros = {
{FaceGazeGesture::MOUTH_PUCKER, MacroName::MOUSE_CLICK_LEFT}};
const base::flat_map<FaceGazeGesture, int> gestures_to_confidences = {
{FaceGazeGesture::MOUTH_PUCKER, 50}};
utils()->EnableFaceGaze(Config().Default().WithBindings(
gestures_to_macros, gestures_to_confidences));
event_handler().ClearEvents();
// Move mouth right to trigger mouse press event.
utils()->ProcessFaceLandmarkerResult(MockFaceLandmarkerResult().WithGesture(
MediapipeGesture::MOUTH_PUCKER, 60));
auto press_events =
event_handler().mouse_events(ui::EventType::kMousePressed);
auto release_events =
event_handler().mouse_events(ui::EventType::kMouseReleased);
ASSERT_EQ(1u, press_events.size());
ASSERT_EQ(1u, release_events.size());
ASSERT_TRUE(press_events.back().IsOnlyLeftMouseButton());
ASSERT_EQ(kCenter, press_events[0].root_location());
ASSERT_TRUE(release_events.back().IsOnlyLeftMouseButton());
ASSERT_EQ(kCenter, release_events[0].root_location());
// Release doesn't trigger anything else.
event_handler().ClearEvents();
utils()->ProcessFaceLandmarkerResult(MockFaceLandmarkerResult().WithGesture(
MediapipeGesture::MOUTH_PUCKER, 30));
ASSERT_EQ(0u, event_handler().mouse_events().size());
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, MouseLongClick) {
const base::flat_map<FaceGazeGesture, MacroName> gestures_to_macros = {
{FaceGazeGesture::MOUTH_RIGHT, MacroName::MOUSE_LONG_CLICK_LEFT}};
const base::flat_map<FaceGazeGesture, int> gestures_to_confidences = {
{FaceGazeGesture::MOUTH_RIGHT, 30}};
utils()->EnableFaceGaze(
Config()
.Default()
.WithBindings(gestures_to_macros, gestures_to_confidences)
.WithGestureRepeatDelayMs(0));
event_handler().ClearEvents();
auto* drag_event_rewriter = ash::Shell::Get()
->accessibility_controller()
->GetDragEventRewriterForTest();
ASSERT_NE(drag_event_rewriter, nullptr);
ASSERT_FALSE(drag_event_rewriter->IsEnabled());
// Move mouth right to trigger mouse press event.
utils()->ProcessFaceLandmarkerResult(MockFaceLandmarkerResult().WithGesture(
MediapipeGesture::MOUTH_RIGHT, 40));
std::vector<ui::MouseEvent> mouse_events =
event_handler().mouse_events(ui::EventType::kMousePressed);
ASSERT_EQ(1u, mouse_events.size());
ASSERT_EQ(ui::EventType::kMousePressed, mouse_events.back().type());
ASSERT_TRUE(mouse_events.back().IsOnlyLeftMouseButton());
ASSERT_EQ(kCenter, mouse_events.back().root_location());
ASSERT_FALSE(mouse_events.back().IsSynthesized());
ASSERT_TRUE(drag_event_rewriter->IsEnabled());
// Move forehead to trigger a kMouseDragged event.
event_handler().ClearEvents();
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult().WithNormalizedForeheadLocation(
std::make_pair(0.9, 0.9)));
utils()->TriggerMouseControllerInterval();
mouse_events = event_handler().mouse_events(ui::EventType::kMouseDragged);
ASSERT_EQ(1u, mouse_events.size());
ASSERT_EQ(ui::EventType::kMouseDragged, mouse_events.back().type());
ASSERT_TRUE(mouse_events.back().IsOnlyLeftMouseButton());
ASSERT_NE(kCenter, mouse_events.back().root_location());
ASSERT_FALSE(mouse_events.back().IsSynthesized());
ASSERT_TRUE(drag_event_rewriter->IsEnabled());
// Move mouth right again to trigger mouse release event.
event_handler().ClearEvents();
utils()->ProcessFaceLandmarkerResult(MockFaceLandmarkerResult().WithGesture(
MediapipeGesture::MOUTH_RIGHT, 40));
mouse_events = event_handler().mouse_events(ui::EventType::kMouseReleased);
ASSERT_EQ(1u, mouse_events.size());
ASSERT_EQ(ui::EventType::kMouseReleased, mouse_events.back().type());
ASSERT_TRUE(mouse_events.back().IsOnlyLeftMouseButton());
ASSERT_NE(kCenter, mouse_events.back().root_location());
ASSERT_FALSE(mouse_events.back().IsSynthesized());
ASSERT_FALSE(drag_event_rewriter->IsEnabled());
}
// TODO(crbug.com/367758998): Re-enable this test.
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, DISABLED_PerformanceHistogram) {
const base::flat_map<FaceGazeGesture, MacroName> gestures_to_macros = {
{FaceGazeGesture::MOUTH_PUCKER, MacroName::MOUSE_CLICK_LEFT}};
const base::flat_map<FaceGazeGesture, int> gestures_to_confidences = {
{FaceGazeGesture::MOUTH_PUCKER, 50}};
utils()->EnableFaceGaze(Config().Default().WithBindings(
gestures_to_macros, gestures_to_confidences));
base::StatisticsRecorder::HistogramWaiter waiter(
"Accessibility.FaceGaze.AverageFaceLandmarkerLatency");
for (int i = 0; i < 100; ++i) {
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult().WithLatency(i));
}
waiter.Wait();
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, OpenSettingsPage) {
const base::flat_map<FaceGazeGesture, MacroName> gestures_to_macros = {
{FaceGazeGesture::MOUTH_RIGHT, MacroName::OPEN_FACEGAZE_SETTINGS}};
const base::flat_map<FaceGazeGesture, int> gestures_to_confidences = {
{FaceGazeGesture::MOUTH_RIGHT, 30}};
utils()->EnableFaceGaze(Config().Default().WithBindings(
gestures_to_macros, gestures_to_confidences));
base::RunLoop waiter;
AccessibilityManager::Get()->SetOpenSettingsSubpageObserverForTest(
base::BindLambdaForTesting([&waiter]() { waiter.Quit(); }));
// Move mouth right to open the FaceGaze settings page.
utils()->ProcessFaceLandmarkerResult(MockFaceLandmarkerResult().WithGesture(
MediapipeGesture::MOUTH_RIGHT, 40));
waiter.Run();
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, ToggleVirtualKeyboard) {
const base::flat_map<FaceGazeGesture, MacroName> gestures_to_macros = {
{FaceGazeGesture::JAW_OPEN, MacroName::TOGGLE_VIRTUAL_KEYBOARD}};
const base::flat_map<FaceGazeGesture, int> gestures_to_confidences = {
{FaceGazeGesture::JAW_OPEN, 30}};
utils()->EnableFaceGaze(Config().Default().WithBindings(
gestures_to_macros, gestures_to_confidences));
base::RunLoop waiter;
ash::Shell::Get()
->accessibility_controller()
->SetVirtualKeyboardVisibleCallbackForTesting(
base::BindLambdaForTesting([&waiter]() { waiter.Quit(); }));
// Open jaw to toggle the virtual keyboard.
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult().WithGesture(MediapipeGesture::JAW_OPEN, 40));
waiter.Run();
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, DoubleClick) {
const base::flat_map<FaceGazeGesture, MacroName> gestures_to_macros = {
{FaceGazeGesture::MOUTH_FUNNEL, MacroName::MOUSE_CLICK_LEFT_DOUBLE}};
const base::flat_map<FaceGazeGesture, int> gestures_to_confidences = {
{FaceGazeGesture::MOUTH_FUNNEL, 50}};
utils()->EnableFaceGaze(Config().Default().WithBindings(
gestures_to_macros, gestures_to_confidences));
event_handler().ClearEvents();
// Mouth funnel to trigger double click event.
utils()->ProcessFaceLandmarkerResult(MockFaceLandmarkerResult().WithGesture(
MediapipeGesture::MOUTH_FUNNEL, 60));
auto press_events =
event_handler().mouse_events(ui::EventType::kMousePressed);
auto release_events =
event_handler().mouse_events(ui::EventType::kMouseReleased);
ASSERT_EQ(1u, press_events.size());
ASSERT_EQ(1u, release_events.size());
const auto& press_event = press_events.back();
const auto& release_event = release_events.back();
ASSERT_TRUE(press_event.IsOnlyLeftMouseButton());
ASSERT_EQ(kCenter, press_event.root_location());
// Assert that the press event is for a double click.
ASSERT_TRUE(ui::EF_IS_DOUBLE_CLICK & press_event.flags());
ASSERT_TRUE(release_event.IsOnlyLeftMouseButton());
ASSERT_EQ(kCenter, release_event.root_location());
// Assert that the release event is for a double click.
ASSERT_TRUE(ui::EF_IS_DOUBLE_CLICK & release_event.flags());
// Release doesn't trigger anything else.
event_handler().ClearEvents();
utils()->ProcessFaceLandmarkerResult(MockFaceLandmarkerResult().WithGesture(
MediapipeGesture::MOUTH_FUNNEL, 30));
ASSERT_EQ(0u, event_handler().mouse_events().size());
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, TripleClick) {
const base::flat_map<FaceGazeGesture, MacroName> gestures_to_macros = {
{FaceGazeGesture::MOUTH_FUNNEL, MacroName::MOUSE_CLICK_LEFT_TRIPLE}};
const base::flat_map<FaceGazeGesture, int> gestures_to_confidences = {
{FaceGazeGesture::MOUTH_FUNNEL, 50}};
utils()->EnableFaceGaze(Config().Default().WithBindings(
gestures_to_macros, gestures_to_confidences));
event_handler().ClearEvents();
// Mouth funnel to trigger triple click event.
utils()->ProcessFaceLandmarkerResult(MockFaceLandmarkerResult().WithGesture(
MediapipeGesture::MOUTH_FUNNEL, 60));
auto press_events =
event_handler().mouse_events(ui::EventType::kMousePressed);
auto release_events =
event_handler().mouse_events(ui::EventType::kMouseReleased);
ASSERT_EQ(1u, press_events.size());
ASSERT_EQ(1u, release_events.size());
const auto& press_event = press_events.back();
const auto& release_event = release_events.back();
ASSERT_TRUE(press_event.IsOnlyLeftMouseButton());
ASSERT_EQ(kCenter, press_event.root_location());
// Assert that the press event is for a triple click.
ASSERT_TRUE(ui::EF_IS_TRIPLE_CLICK & press_event.flags());
ASSERT_TRUE(release_event.IsOnlyLeftMouseButton());
ASSERT_EQ(kCenter, release_event.root_location());
// Assert that the release event is for a triple click.
ASSERT_TRUE(ui::EF_IS_TRIPLE_CLICK & release_event.flags());
// Release doesn't trigger anything else.
event_handler().ClearEvents();
utils()->ProcessFaceLandmarkerResult(MockFaceLandmarkerResult().WithGesture(
MediapipeGesture::MOUTH_FUNNEL, 30));
ASSERT_EQ(0u, event_handler().mouse_events().size());
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, AcceptDialog) {
auto* controller = ash::Shell::Get()->accessibility_controller();
auto* prefs = GetPrefs();
base::RunLoop dialog_waiter;
controller->AddShowConfirmationDialogCallbackForTesting(
base::BindLambdaForTesting([&dialog_waiter]() { dialog_waiter.Quit(); }));
// Enabling FaceGaze should show the confirmation dialog.
utils()->EnableFaceGaze(Config().Default().WithDialogAccepted(false));
dialog_waiter.Run();
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_FALSE(prefs->GetBoolean(
prefs::kAccessibilityFaceGazeAcceleratorDialogHasBeenAccepted));
ASSERT_NE(nullptr, controller->GetConfirmationDialogForTest());
base::RunLoop settings_waiter;
AccessibilityManager::Get()->SetOpenSettingsSubpageObserverForTest(
base::BindLambdaForTesting(
[&settings_waiter]() { settings_waiter.Quit(); }));
// Accepting the dialog should initialize the FaceLandmarker and open the
// settings page.
controller->GetConfirmationDialogForTest()->Accept();
utils()->WaitForFaceLandmarker();
settings_waiter.Run();
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
// Verify that the dialog accepted pref is now true.
ASSERT_TRUE(prefs->GetBoolean(
prefs::kAccessibilityFaceGazeAcceleratorDialogHasBeenAccepted));
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, CancelDialog) {
auto* controller = ash::Shell::Get()->accessibility_controller();
auto* prefs = GetPrefs();
base::RunLoop dialog_waiter;
controller->AddShowConfirmationDialogCallbackForTesting(
base::BindLambdaForTesting([&dialog_waiter]() { dialog_waiter.Quit(); }));
// Enabling FaceGaze should show the confirmation dialog.
utils()->EnableFaceGaze(Config().Default().WithDialogAccepted(false));
dialog_waiter.Run();
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_FALSE(prefs->GetBoolean(
prefs::kAccessibilityFaceGazeAcceleratorDialogHasBeenAccepted));
ASSERT_NE(nullptr, controller->GetConfirmationDialogForTest());
base::RunLoop pref_waiter;
PrefChangeRegistrar change_observer;
change_observer.Init(prefs);
change_observer.Add(prefs::kAccessibilityFaceGazeEnabled,
pref_waiter.QuitClosure());
// Canceling the dialog should turn off FaceGaze.
controller->GetConfirmationDialogForTest()->Cancel();
pref_waiter.Run();
ASSERT_FALSE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
// Verify that the dialog accepted pref is still false.
ASSERT_FALSE(prefs->GetBoolean(
prefs::kAccessibilityFaceGazeAcceleratorDialogHasBeenAccepted));
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, ScrollMode) {
const base::flat_map<FaceGazeGesture, MacroName> gestures_to_macros = {
{FaceGazeGesture::JAW_LEFT, MacroName::TOGGLE_SCROLL_MODE}};
const base::flat_map<FaceGazeGesture, int> gestures_to_confidences = {
{FaceGazeGesture::JAW_LEFT, 30}};
utils()->EnableFaceGaze(
Config()
.Default()
.WithBindings(gestures_to_macros, gestures_to_confidences)
// Ensure speeds are high so that head movements exceed the scroll
// mode movement threshold.
.WithCursorSpeeds({/*up=*/5, /*down=*/5, /*left=*/5, /*right=*/5})
.WithGestureRepeatDelayMs(0));
// Move jaw left to enter scroll mode.
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult().WithGesture(MediapipeGesture::JAW_LEFT, 40));
utils()->AssertScrollMode(true);
event_handler().ClearEvents();
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult()
.WithNormalizedForeheadLocation(std::make_pair(0.9, 0.9))
.WithGesture(MediapipeGesture::JAW_LEFT, 0));
utils()->TriggerMouseControllerInterval();
// Head movement should cause one scroll event to be sent.
ASSERT_EQ(1u,
event_handler().mouse_events(ui::EventType::kMousewheel).size());
// No mouse movement events should be sent.
ASSERT_EQ(0u,
event_handler().mouse_events(ui::EventType::kMouseMoved).size());
// Move jaw left again to exit scroll mode.
utils()->ProcessFaceLandmarkerResult(
MockFaceLandmarkerResult().WithGesture(MediapipeGesture::JAW_LEFT, 40));
utils()->AssertScrollMode(false);
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, DefaultBehavior) {
utils()->EnableFaceGaze(Config().Default());
// Default gesture-to-macro and gesture-to-confidence mappings should be
// installed if we didn't specify them.
const auto& gestures_to_macros =
GetPrefs()->GetDict(prefs::kAccessibilityFaceGazeGesturesToMacros);
const auto& gestures_to_confidences =
GetPrefs()->GetDict(prefs::kAccessibilityFaceGazeGesturesToConfidence);
ASSERT_EQ(gestures_to_macros.size(), 2u);
ASSERT_EQ(gestures_to_confidences.size(), 2u);
ASSERT_EQ(/* MOUSE_CLICK_LEFT */ 35,
gestures_to_macros.FindInt(
FaceGazeTestUtils::ToString(FaceGazeGesture::MOUTH_SMILE)));
ASSERT_EQ(/* SCROLL */ 50,
gestures_to_macros.FindInt(
FaceGazeTestUtils::ToString(FaceGazeGesture::JAW_OPEN)));
ASSERT_EQ(60, gestures_to_confidences.FindInt(
FaceGazeTestUtils::ToString(FaceGazeGesture::MOUTH_SMILE)));
ASSERT_EQ(60, gestures_to_confidences.FindInt(
FaceGazeTestUtils::ToString(FaceGazeGesture::JAW_OPEN)));
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, DisableDialogAccept) {
auto* controller = ash::Shell::Get()->accessibility_controller();
auto* prefs = GetPrefs();
base::RunLoop dialog_waiter;
controller->AddFeatureDisableDialogCallbackForTesting(
base::BindLambdaForTesting([&dialog_waiter]() { dialog_waiter.Quit(); }));
// Enabling FaceGaze should not show the feature disable dialog.
utils()->EnableFaceGaze(Config().Default());
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
// Showing the feature disable dialog should leave the enabled pref unchanged.
controller->RequestDisableFaceGaze();
dialog_waiter.Run();
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_NE(nullptr, controller->GetFeatureDisableDialogForTest());
base::RunLoop pref_waiter;
PrefChangeRegistrar change_observer;
change_observer.Init(prefs);
change_observer.Add(prefs::kAccessibilityFaceGazeEnabled,
pref_waiter.QuitClosure());
// Accepting the dialog should turn off FaceGaze.
controller->GetFeatureDisableDialogForTest()->Accept();
pref_waiter.Run();
ASSERT_FALSE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, DisableDialogCancel) {
auto* controller = ash::Shell::Get()->accessibility_controller();
auto* prefs = GetPrefs();
base::RunLoop dialog_waiter;
controller->AddFeatureDisableDialogCallbackForTesting(
base::BindLambdaForTesting([&dialog_waiter]() { dialog_waiter.Quit(); }));
// Enabling FaceGaze should not show the feature disable dialog.
utils()->EnableFaceGaze(Config().Default());
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
// Showing the feature disable dialog should leave the enabled pref unchanged.
controller->RequestDisableFaceGaze();
dialog_waiter.Run();
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_NE(nullptr, controller->GetFeatureDisableDialogForTest());
base::RunLoop pref_waiter;
PrefChangeRegistrar change_observer;
change_observer.Init(prefs);
change_observer.Add(prefs::kAccessibilityFaceGazeEnabledSentinel,
pref_waiter.QuitClosure());
// Cancelling the dialog should leave FaceGaze on.
controller->GetFeatureDisableDialogForTest()->Cancel();
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
}
// TODO(crbug.com/383757982): Add test API for .WithCursorControlEnabled() and
// .WithActionsEnabled() and update tests accordingly.
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, EnableCursorControlNoDialog) {
auto* controller = ash::Shell::Get()->accessibility_controller();
auto* prefs = GetPrefs();
base::RunLoop dialog_waiter;
controller->AddFeatureDisableDialogCallbackForTesting(
base::BindLambdaForTesting([&dialog_waiter]() { dialog_waiter.Quit(); }));
// Enabling FaceGaze should not show the feature disable dialog.
utils()->EnableFaceGaze(Config().Default());
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
// Setting sentinel value to true should not show the feature disable dialog.
prefs->SetBoolean(prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel,
true);
ASSERT_TRUE(
prefs->GetBoolean(prefs::kAccessibilityFaceGazeCursorControlEnabled));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest,
DisableCursorControlDialogAccept) {
auto* controller = ash::Shell::Get()->accessibility_controller();
auto* prefs = GetPrefs();
base::RunLoop dialog_waiter;
controller->AddFeatureDisableDialogCallbackForTesting(
base::BindLambdaForTesting([&dialog_waiter]() { dialog_waiter.Quit(); }));
// Enabling FaceGaze should not show the feature disable dialog.
utils()->EnableFaceGaze(Config().Default());
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
prefs->SetBoolean(prefs::kAccessibilityFaceGazeCursorControlEnabled, true);
ASSERT_TRUE(prefs->GetBoolean(
prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel));
// Setting sentinel value to false should show the feature disable dialog and
// leave the behavior pref unchanged.
prefs->SetBoolean(prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel,
false);
dialog_waiter.Run();
ASSERT_TRUE(
prefs->GetBoolean(prefs::kAccessibilityFaceGazeCursorControlEnabled));
ASSERT_FALSE(prefs->GetBoolean(
prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel));
ASSERT_NE(nullptr, controller->GetFeatureDisableDialogForTest());
base::RunLoop pref_waiter;
PrefChangeRegistrar change_observer;
change_observer.Init(prefs);
change_observer.Add(prefs::kAccessibilityFaceGazeCursorControlEnabled,
pref_waiter.QuitClosure());
// Accepting the dialog should turn off FaceGaze cursor control.
controller->GetFeatureDisableDialogForTest()->Accept();
pref_waiter.Run();
// Assert behavior and sentinel prefs are in sync.
ASSERT_FALSE(
prefs->GetBoolean(prefs::kAccessibilityFaceGazeCursorControlEnabled));
ASSERT_FALSE(prefs->GetBoolean(
prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel));
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest,
DisableCursorControlDialogCancel) {
auto* controller = ash::Shell::Get()->accessibility_controller();
auto* prefs = GetPrefs();
base::RunLoop dialog_waiter;
controller->AddFeatureDisableDialogCallbackForTesting(
base::BindLambdaForTesting([&dialog_waiter]() { dialog_waiter.Quit(); }));
// Enabling FaceGaze should not show the feature disable dialog.
utils()->EnableFaceGaze(Config().Default());
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
prefs->SetBoolean(prefs::kAccessibilityFaceGazeCursorControlEnabled, true);
ASSERT_TRUE(prefs->GetBoolean(
prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel));
// Setting sentinel value to false should show the feature disable dialog and
// leave the behavior pref unchanged.
prefs->SetBoolean(prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel,
false);
dialog_waiter.Run();
ASSERT_TRUE(
prefs->GetBoolean(prefs::kAccessibilityFaceGazeCursorControlEnabled));
ASSERT_NE(nullptr, controller->GetFeatureDisableDialogForTest());
base::RunLoop pref_waiter;
PrefChangeRegistrar change_observer;
change_observer.Init(prefs);
change_observer.Add(prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel,
pref_waiter.QuitClosure());
// Cancelling the dialog should leave FaceGaze cursor control on and set the
// sentinel to true.
controller->GetFeatureDisableDialogForTest()->Cancel();
pref_waiter.Run();
// Assert behavior and sentinel prefs are in sync.
ASSERT_TRUE(
prefs->GetBoolean(prefs::kAccessibilityFaceGazeCursorControlEnabled));
ASSERT_TRUE(prefs->GetBoolean(
prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel));
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, EnableActionsNoDialog) {
auto* controller = ash::Shell::Get()->accessibility_controller();
auto* prefs = GetPrefs();
base::RunLoop dialog_waiter;
controller->AddFeatureDisableDialogCallbackForTesting(
base::BindLambdaForTesting([&dialog_waiter]() { dialog_waiter.Quit(); }));
// Enabling FaceGaze should not show the feature disable dialog.
utils()->EnableFaceGaze(Config().Default());
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
// Setting sentinel value to true should not show the feature disable dialog.
prefs->SetBoolean(prefs::kAccessibilityFaceGazeActionsEnabledSentinel, true);
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeActionsEnabled));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, DisableActionsDialogAccept) {
auto* controller = ash::Shell::Get()->accessibility_controller();
auto* prefs = GetPrefs();
base::RunLoop dialog_waiter;
controller->AddFeatureDisableDialogCallbackForTesting(
base::BindLambdaForTesting([&dialog_waiter]() { dialog_waiter.Quit(); }));
// Setting sentinel value to true should not show the feature disable dialog.
utils()->EnableFaceGaze(Config().Default());
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
prefs->SetBoolean(prefs::kAccessibilityFaceGazeActionsEnabled, true);
ASSERT_TRUE(
prefs->GetBoolean(prefs::kAccessibilityFaceGazeActionsEnabledSentinel));
// Setting sentinel value to false should show the feature disable dialog and
// leave the behavior pref unchanged.
prefs->SetBoolean(prefs::kAccessibilityFaceGazeActionsEnabledSentinel, false);
dialog_waiter.Run();
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeActionsEnabled));
ASSERT_FALSE(
prefs->GetBoolean(prefs::kAccessibilityFaceGazeActionsEnabledSentinel));
ASSERT_NE(nullptr, controller->GetFeatureDisableDialogForTest());
base::RunLoop pref_waiter;
PrefChangeRegistrar change_observer;
change_observer.Init(prefs);
change_observer.Add(prefs::kAccessibilityFaceGazeActionsEnabled,
pref_waiter.QuitClosure());
// Accepting the dialog should turn off FaceGaze actions.
controller->GetFeatureDisableDialogForTest()->Accept();
pref_waiter.Run();
// Assert behavior and sentinel prefs are in sync.
ASSERT_FALSE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeActionsEnabled));
ASSERT_FALSE(
prefs->GetBoolean(prefs::kAccessibilityFaceGazeActionsEnabledSentinel));
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, DisableActionsDialogCancel) {
auto* controller = ash::Shell::Get()->accessibility_controller();
auto* prefs = GetPrefs();
base::RunLoop dialog_waiter;
controller->AddFeatureDisableDialogCallbackForTesting(
base::BindLambdaForTesting([&dialog_waiter]() { dialog_waiter.Quit(); }));
// Setting sentinel value to true should not show the feature disable dialog.
utils()->EnableFaceGaze(Config().Default());
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
prefs->SetBoolean(prefs::kAccessibilityFaceGazeActionsEnabled, true);
ASSERT_TRUE(
prefs->GetBoolean(prefs::kAccessibilityFaceGazeActionsEnabledSentinel));
// Setting sentinel value to false should show the feature disable dialog and
// leave the behavior pref unchanged.
prefs->SetBoolean(prefs::kAccessibilityFaceGazeActionsEnabledSentinel, false);
dialog_waiter.Run();
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeActionsEnabled));
ASSERT_NE(nullptr, controller->GetFeatureDisableDialogForTest());
base::RunLoop pref_waiter;
PrefChangeRegistrar change_observer;
change_observer.Init(prefs);
change_observer.Add(prefs::kAccessibilityFaceGazeActionsEnabledSentinel,
pref_waiter.QuitClosure());
// Cancelling the dialog should leave FaceGaze actions on and set the sentinel
// to true.
controller->GetFeatureDisableDialogForTest()->Cancel();
pref_waiter.Run();
// Assert behavior and sentinel prefs are in sync.
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeActionsEnabled));
ASSERT_TRUE(
prefs->GetBoolean(prefs::kAccessibilityFaceGazeActionsEnabledSentinel));
}
// TODO(crbug.com/423267032): Fix and re-enable flaky test.
#if defined(MEMORY_SANITIZER)
#define MAYBE_CloseButton DISABLED_CloseButton
#else
#define MAYBE_CloseButton CloseButton
#endif
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, MAYBE_CloseButton) {
auto* controller = ash::Shell::Get()->accessibility_controller();
auto* prefs = GetPrefs();
base::RunLoop dialog_waiter;
controller->AddFeatureDisableDialogCallbackForTesting(
base::BindLambdaForTesting([&dialog_waiter]() { dialog_waiter.Quit(); }));
// Setup FaceGaze.
const base::flat_map<FaceGazeGesture, MacroName> gestures_to_macros = {
{FaceGazeGesture::MOUTH_PUCKER, MacroName::MOUSE_CLICK_LEFT}};
const base::flat_map<FaceGazeGesture, int> gestures_to_confidences = {
{FaceGazeGesture::MOUTH_PUCKER, 50}};
utils()->EnableFaceGaze(Config().Default().WithBindings(
gestures_to_macros, gestures_to_confidences));
// Assert initial state.
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
// Move mouse to close button.
gfx::Point close_button = bubble_helper()->GetCloseButtonCenterPoint();
utils()->MoveMouseTo(close_button);
utils()->AssertCursorAt(close_button);
ASSERT_TRUE(bubble_helper()->IsVisible());
// Clicking the close button will show the dialog to turn off FaceGaze. Note
// that the feature remains on until the dialog is accepted.
utils()->ProcessFaceLandmarkerResult(MockFaceLandmarkerResult().WithGesture(
MediapipeGesture::MOUTH_PUCKER, 95));
dialog_waiter.Run();
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_NE(nullptr, controller->GetFeatureDisableDialogForTest());
base::RunLoop pref_waiter;
PrefChangeRegistrar change_observer;
change_observer.Init(prefs);
change_observer.Add(prefs::kAccessibilityFaceGazeEnabled,
pref_waiter.QuitClosure());
// Accepting the dialog should turn off FaceGaze.
controller->GetFeatureDisableDialogForTest()->Accept();
pref_waiter.Run();
}
} // namespace ash