// 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.
#ifndef CHROME_BROWSER_ASH_ACCESSIBILITY_FACEGAZE_TEST_UTILS_H_
#define CHROME_BROWSER_ASH_ACCESSIBILITY_FACEGAZE_TEST_UTILS_H_
#include <memory>
#include <optional>
#include <string>
#include "base/containers/flat_map.h"
#include "base/values.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/point_f.h"
namespace gfx {
class Point;
} // namespace gfx
namespace ash {
// A class that can be used to exercise FaceGaze in browsertests.
class FaceGazeTestUtils {
public:
// The facial gestures that are supported by FaceGaze. Ensure this enum stays
// in sync with the source of truth in
// ash/webui/common/resources/accessibility/facial_gestures.ts.
enum class FaceGazeGesture {
BROW_INNER_UP,
BROWS_DOWN,
EYE_SQUINT_LEFT,
EYE_SQUINT_RIGHT,
EYES_BLINK,
EYES_LOOK_DOWN,
EYES_LOOK_LEFT,
EYES_LOOK_RIGHT,
EYES_LOOK_UP,
JAW_LEFT,
JAW_OPEN,
JAW_RIGHT,
MOUTH_FUNNEL,
MOUTH_LEFT,
MOUTH_PUCKER,
MOUTH_RIGHT,
MOUTH_SMILE,
MOUTH_UPPER_UP,
};
// Macros used by accessibility features on ChromeOS.
// Ensure this enum stays in sync with the source of truth in
// ash/webui/common/resources/accessibility/macro_names.ts.
enum MacroName {
UNSPECIFIED = 0,
INPUT_TEXT_VIEW = 1,
DELETE_PREV_CHAR = 2,
NAV_PREV_CHAR = 3,
NAV_NEXT_CHAR = 4,
NAV_PREV_LINE = 5,
NAV_NEXT_LINE = 6,
COPY_SELECTED_TEXT = 7,
PASTE_TEXT = 8,
CUT_SELECTED_TEXT = 9,
UNDO_TEXT_EDIT = 10,
REDO_ACTION = 11,
SELECT_ALL_TEXT = 12,
UNSELECT_TEXT = 13,
LIST_COMMANDS = 14,
NEW_LINE = 15,
TOGGLE_DICTATION = 16,
DELETE_PREV_WORD = 17,
DELETE_PREV_SENT = 18,
NAV_NEXT_WORD = 19,
NAV_PREV_WORD = 20,
SMART_DELETE_PHRASE = 21,
SMART_REPLACE_PHRASE = 22,
SMART_INSERT_BEFORE = 23,
SMART_SELECT_BTWN_INCL = 24,
NAV_NEXT_SENT = 25,
NAV_PREV_SENT = 26,
DELETE_ALL_TEXT = 27,
NAV_START_TEXT = 28,
NAV_END_TEXT = 29,
SELECT_PREV_WORD = 30,
SELECT_NEXT_WORD = 31,
SELECT_NEXT_CHAR = 32,
SELECT_PREV_CHAR = 33,
REPEAT = 34,
MOUSE_CLICK_LEFT = 35,
MOUSE_CLICK_RIGHT = 36,
RESET_CURSOR = 37,
KEY_PRESS_SPACE = 38,
KEY_PRESS_LEFT = 39,
KEY_PRESS_RIGHT = 40,
KEY_PRESS_UP = 41,
KEY_PRESS_DOWN = 42,
MOUSE_LONG_CLICK_LEFT = 45,
TOGGLE_FACEGAZE = 46,
OPEN_FACEGAZE_SETTINGS = 47,
TOGGLE_VIRTUAL_KEYBOARD = 48,
MOUSE_CLICK_LEFT_DOUBLE = 49,
TOGGLE_SCROLL_MODE = 50,
CUSTOM_KEY_COMBINATION = 51,
};
// Facial gestures recognized by Mediapipe. Ensure this enum stays in sync
// with the source of truth in chrome/browser/resources/chromeos/\
// accessibility/accessibility_common/facegaze/gesture_detector.ts.
enum class MediapipeGesture {
BROW_DOWN_LEFT,
BROW_DOWN_RIGHT,
BROW_INNER_UP,
EYE_BLINK_LEFT,
EYE_BLINK_RIGHT,
EYE_LOOK_DOWN_LEFT,
EYE_LOOK_DOWN_RIGHT,
EYE_LOOK_IN_LEFT,
EYE_LOOK_IN_RIGHT,
EYE_LOOK_OUT_LEFT,
EYE_LOOK_OUT_RIGHT,
EYE_LOOK_UP_LEFT,
EYE_LOOK_UP_RIGHT,
EYE_SQUINT_LEFT,
EYE_SQUINT_RIGHT,
JAW_LEFT,
JAW_OPEN,
JAW_RIGHT,
MOUTH_FUNNEL,
MOUTH_LEFT,
MOUTH_PUCKER,
MOUTH_RIGHT,
MOUTH_SMILE_LEFT,
MOUTH_SMILE_RIGHT,
MOUTH_UPPER_UP_LEFT,
MOUTH_UPPER_UP_RIGHT,
};
// A struct that holds cursor speed values.
struct CursorSpeeds {
int up;
int down;
int left;
int right;
};
// A class that helps initialize FaceGaze with a configuration.
class Config {
public:
Config();
~Config();
Config(const Config&) = delete;
Config& operator=(const Config&) = delete;
// Returns a Config that sets required properties to default values.
Config& Default();
Config& WithForeheadLocation(const gfx::PointF& location);
Config& WithCursorLocation(const gfx::Point& location);
Config& WithBufferSize(int size);
Config& WithCursorAcceleration(bool acceleration);
Config& WithDialogAccepted(bool accepted);
Config& WithGesturesToMacros(
const base::flat_map<FaceGazeGesture, MacroName>& gestures_to_macros);
Config& WithGestureConfidences(
const base::flat_map<FaceGazeGesture, int>& gesture_confidences);
Config& WithCursorSpeeds(const CursorSpeeds& speeds);
Config& WithGestureRepeatDelayMs(int delay);
const gfx::PointF& forehead_location() const { return forehead_location_; }
const gfx::Point& cursor_location() const { return cursor_location_; }
int buffer_size() const { return buffer_size_; }
bool use_cursor_acceleration() const { return use_cursor_acceleration_; }
bool dialog_accepted() const { return dialog_accepted_; }
const std::optional<base::flat_map<FaceGazeGesture, MacroName>>&
gestures_to_macros() const {
return gestures_to_macros_;
}
const std::optional<base::flat_map<FaceGazeGesture, int>>&
gesture_confidences() const {
return gesture_confidences_;
}
const std::optional<CursorSpeeds>& cursor_speeds() const {
return cursor_speeds_;
}
std::optional<int> gesture_repeat_delay_ms() const {
return gesture_repeat_delay_ms_;
}
private:
// Required properties.
gfx::PointF forehead_location_;
gfx::Point cursor_location_;
int buffer_size_;
bool use_cursor_acceleration_;
bool dialog_accepted_;
// Optional properties.
std::optional<base::flat_map<FaceGazeGesture, MacroName>>
gestures_to_macros_;
std::optional<base::flat_map<FaceGazeGesture, int>> gesture_confidences_;
std::optional<CursorSpeeds> cursor_speeds_;
std::optional<int> gesture_repeat_delay_ms_;
};
// A class that represents a fake FaceLandmarkerResult.
class MockFaceLandmarkerResult {
public:
MockFaceLandmarkerResult();
~MockFaceLandmarkerResult();
MockFaceLandmarkerResult(const MockFaceLandmarkerResult&) = delete;
MockFaceLandmarkerResult& operator=(const MockFaceLandmarkerResult&) =
delete;
MockFaceLandmarkerResult& WithNormalizedForeheadLocation(double x,
double y);
MockFaceLandmarkerResult& WithGesture(const MediapipeGesture& gesture,
int confidence);
MockFaceLandmarkerResult& WithLatency(int latency);
const base::Value::Dict& forehead_location() const {
return forehead_location_;
}
const base::Value::List& recognized_gestures() const {
return recognized_gestures_;
}
const std::optional<int>& latency() const { return latency_; }
private:
std::optional<int> latency_;
base::Value::Dict forehead_location_;
base::Value::List recognized_gestures_;
};
FaceGazeTestUtils();
~FaceGazeTestUtils();
FaceGazeTestUtils(const FaceGazeTestUtils&) = delete;
FaceGazeTestUtils& operator=(const FaceGazeTestUtils&) = delete;
// Enables, sets up, and configures FaceGaze with the given configuration.
void EnableFaceGaze(const Config& config);
// Waits for the cursor location to propagate to the FaceGaze MouseController.
void WaitForCursorPosition(const gfx::Point& location);
// Forces FaceGaze to process `result`, since tests don't have access to real
// camera data.
void ProcessFaceLandmarkerResult(const MockFaceLandmarkerResult& result);
// The MouseController updates the cursor location at a set interval. To
// increase test stability, the interval is canceled in tests, and must be
// triggered manually using this method.
void TriggerMouseControllerInterval();
void MoveMouseTo(const gfx::Point& location);
void AssertCursorAt(const gfx::Point& location);
void AssertScrollMode(bool active);
private:
void ExecuteAccessibilityCommonScript(const std::string& script);
// Setup-related methods.
void SetUpMediapipeDir();
void WaitForJSReady();
void SkipInitializeWebCamFaceLandmarker();
void SetUpJSTestSupport();
void CancelMouseControllerInterval();
// Creates and initializes the FaceLandmarker API within the extension.
void CreateFaceLandmarker();
void ConfigureFaceGaze(const Config& config);
// Preference-related methods.
void SetCursorSpeeds(const CursorSpeeds& speeds);
void SetBufferSize(int size);
void SetCursorAcceleration(bool use_acceleration);
void SetGesturesToMacros(
const base::flat_map<FaceGazeGesture, MacroName>& gestures_to_macros);
void SetGestureConfidences(
const base::flat_map<FaceGazeGesture, int>& gesture_confidences);
// Sets the gesture repeat delay threshold.
void SetGestureRepeatDelayMs(int delay);
std::unique_ptr<ui::test::EventGenerator> event_generator_;
};
} // namespace ash
#endif // CHROME_BROWSER_ASH_ACCESSIBILITY_FACEGAZE_TEST_UTILS_H_