0

Reland "[FaceGaze] Add confirmation dialogs for feature/cursor/actions buttons"

This is a reland of commit c41380632c

The original commit was reverted under suspicion of introducing
test failures on linux-chromeos-dbg, but the test failures
persisted after revert and were definitively traced back to a
different commit. Relanding this CL as-is.

crbug.com/384012319 filed for the test failures.

Bug: 371199351

Original change's description:
> [FaceGaze] Add confirmation dialogs for feature/cursor/actions buttons
>
> * Show standard confirmation dialog when feature is toggled off.
> * Show standard confirmation dialog when cursor control is toggled off.
> * Show standard confirmation dialog when actions are toggled off.
> * To enable user to use FaceGaze to interact with the dialog,
> add a set of sentinel prefs to indicate when users have toggled
> the button off.
> * To enable JS to force feature off without showing a dialog,
> add a sentinel pref for showing a confirmation dialog that is
> set to false when FaceGaze is forcing the feature off.
> * Update all JS to only interact with the set of sentinel prefs.
> * Add tests.
> * Leave cursor/actions strings as translateable=false for now.
>
> Design doc:
> https://docs.google.com/document/d/1ULHXI_QK6ET1Inyrm_9DrIpiecVFc3Z7kyyv6cs0oJ0/edit?usp=sharing
>
> Screenshots:
> https://screenshot.googleplex.com/3f6ir9GbJC7zd5q
> https://screenshot.googleplex.com/ra4nbokD8K4Cpub
> https://screenshot.googleplex.com/6fd2gzZvx8Gmg3W
>
> Change-Id: Iccb932657e9613cef670f2638e5dfa0794d0e3db
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6042086
> Reviewed-by: Akihiro Ota <akihiroota@chromium.org>
> Commit-Queue: Amanda Lin Dietz <aldietz@google.com>
> Reviewed-by: Xiaohui Chen <xiaohuic@chromium.org>
> Reviewed-by: James Cook <jamescook@chromium.org>
> Reviewed-by: Kyle Horimoto <khorimoto@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1395618}

Change-Id: I2a6ff4a9e251c86b66ffd36af778e06531423ce4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6101831
Reviewed-by: James Cook <jamescook@chromium.org>
Auto-Submit: Amanda Lin Dietz <aldietz@google.com>
Reviewed-by: Akihiro Ota <akihiroota@chromium.org>
Reviewed-by: Kyle Horimoto <khorimoto@chromium.org>
Commit-Queue: Amanda Lin Dietz <aldietz@google.com>
Reviewed-by: Xiaohui Chen <xiaohuic@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1398090}
This commit is contained in:
Amanda Lin Dietz
2024-12-18 11:07:54 -08:00
committed by Chromium LUCI CQ
parent 8bb05339e0
commit b59764323f
19 changed files with 623 additions and 18 deletions

@ -1254,6 +1254,14 @@ void AccessibilityController::RegisterProfilePrefs(
registry->RegisterBooleanPref(
prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled, false);
registry->RegisterBooleanPref(prefs::kAccessibilityFaceGazeEnabled, false);
registry->RegisterBooleanPref(prefs::kAccessibilityFaceGazeEnabledSentinel,
false);
registry->RegisterBooleanPref(
prefs::kAccessibilityFaceGazeEnabledSentinelShowDialog, true);
registry->RegisterBooleanPref(
prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel, true);
registry->RegisterBooleanPref(
prefs::kAccessibilityFaceGazeActionsEnabledSentinel, true);
registry->RegisterBooleanPref(prefs::kAccessibilityDisableTrackpadEnabled,
false);
registry->RegisterIntegerPref(prefs::kAccessibilityDisableTrackpadMode,
@ -2574,6 +2582,8 @@ void AccessibilityController::ObservePrefs(PrefService* prefs) {
// Features will be initialized from current prefs later.
}
// TODO(crbug.com/383754550): Consider updating calls from
// base::Unretained(this) to GetWeakPtr().
pref_change_registrar_->Add(
prefs::kAccessibilityAutoclickDelayMs,
base::BindRepeating(
@ -2766,6 +2776,25 @@ void AccessibilityController::ObservePrefs(PrefService* prefs) {
if (::features::IsAccessibilityFaceGazeEnabled()) {
UpdateFaceGazeFromPrefs();
pref_change_registrar_->Add(
prefs::kAccessibilityFaceGazeEnabledSentinel,
base::BindRepeating(&AccessibilityController::OnFaceGazeSentinelChanged,
base::Unretained(this),
prefs::kAccessibilityFaceGazeEnabledSentinel,
prefs::kAccessibilityFaceGazeEnabled));
pref_change_registrar_->Add(
prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel,
base::BindRepeating(
&AccessibilityController::OnFaceGazeSentinelChanged,
base::Unretained(this),
prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel,
prefs::kAccessibilityFaceGazeCursorControlEnabled));
pref_change_registrar_->Add(
prefs::kAccessibilityFaceGazeActionsEnabledSentinel,
base::BindRepeating(&AccessibilityController::OnFaceGazeSentinelChanged,
base::Unretained(this),
prefs::kAccessibilityFaceGazeActionsEnabledSentinel,
prefs::kAccessibilityFaceGazeActionsEnabled));
}
if (::features::IsAccessibilityFlashScreenFeatureEnabled()) {
UpdateFlashNotificationsFromPrefs();
@ -3004,6 +3033,43 @@ void AccessibilityController::UpdateFaceGazeFromPrefs() {
if (!::features::IsAccessibilityFaceGazeEnabled()) {
return;
}
const bool enabled =
active_user_prefs_->GetBoolean(prefs::kAccessibilityFaceGazeEnabled);
const bool sentinel_enabled = active_user_prefs_->GetBoolean(
prefs::kAccessibilityFaceGazeEnabledSentinel);
// Sentinel and behavior pref are not in sync.
if (enabled != sentinel_enabled) {
if (enabled) {
active_user_prefs_->SetBoolean(
prefs::kAccessibilityFaceGazeEnabledSentinel, true);
} else {
// Set sentinel pref to false without showing the dialog.
active_user_prefs_->SetBoolean(
prefs::kAccessibilityFaceGazeEnabledSentinelShowDialog, false);
active_user_prefs_->SetBoolean(
prefs::kAccessibilityFaceGazeEnabledSentinel, false);
}
}
const bool cursor_control_enabled = active_user_prefs_->GetBoolean(
prefs::kAccessibilityFaceGazeCursorControlEnabled);
const bool cursor_control_sentinel_enabled = active_user_prefs_->GetBoolean(
prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel);
if (cursor_control_enabled != cursor_control_sentinel_enabled) {
active_user_prefs_->SetBoolean(
prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel,
cursor_control_enabled);
}
const bool actions_enabled = active_user_prefs_->GetBoolean(
prefs::kAccessibilityFaceGazeActionsEnabled);
const bool actions_sentinel_enabled = active_user_prefs_->GetBoolean(
prefs::kAccessibilityFaceGazeActionsEnabledSentinel);
if (actions_enabled != actions_sentinel_enabled) {
active_user_prefs_->SetBoolean(
prefs::kAccessibilityFaceGazeActionsEnabledSentinel, actions_enabled);
}
}
void AccessibilityController::UpdateFlashNotificationsFromPrefs() {
@ -3626,6 +3692,28 @@ gfx::Rect AccessibilityController::GetConfirmationDialogBoundsInScreen() {
return confirmation_dialog_.get()->GetWidget()->GetWindowBoundsInScreen();
}
void AccessibilityController::ShowFeatureDisableDialog(
int window_title_text_id,
base::OnceClosure on_accept_callback,
base::OnceClosure on_cancel_callback) {
if (disable_dialog_) {
// If a dialog is already being shown we do not show a new one.
// Instead, run the on_close_callback on the new dialog to indicate
// it was closed without the user taking any action.
// This is consistent with AcceleratorController.
std::move(on_cancel_callback).Run();
return;
}
auto* dialog = new AccessibilityFeatureDisableDialog(
window_title_text_id, std::move(on_accept_callback),
std::move(on_cancel_callback));
disable_dialog_ = dialog->GetWeakPtr();
if (show_disable_dialog_callback_for_testing_) {
show_disable_dialog_callback_for_testing_.Run();
}
}
void AccessibilityController::PreviewFlashNotification() const {
flash_screen_controller_->PreviewFlash();
}
@ -3938,6 +4026,11 @@ void AccessibilityController::AddShowConfirmationDialogCallbackForTesting(
show_confirmation_dialog_callback_for_testing_ = std::move(callback);
}
void AccessibilityController::AddFeatureDisableDialogCallbackForTesting(
base::RepeatingCallback<void()> callback) {
show_disable_dialog_callback_for_testing_ = std::move(callback);
}
bool AccessibilityController::VerifyFeaturesDataForTesting() {
return VerifyFeaturesData();
}
@ -3981,6 +4074,62 @@ void AccessibilityController::ScrollAtPoint(
std::ignore = host->GetEventSink()->OnEventFromSource(&wheel);
}
void AccessibilityController::OnFaceGazeSentinelChanged(
const std::string& sentinel_pref,
const std::string& behavior_pref) {
DCHECK(active_user_prefs_);
if (active_user_prefs_->GetBoolean(sentinel_pref)) {
active_user_prefs_->SetBoolean(behavior_pref, true);
return;
}
// Set to FaceGaze disable confirmation text by default to ensure a valid
// value.
int window_title_text_id = IDS_ASH_FACEGAZE_DISABLE_CONFIRMATION_TEXT;
if (sentinel_pref == prefs::kAccessibilityFaceGazeEnabledSentinel) {
const bool show_dialog = active_user_prefs_->GetBoolean(
prefs::kAccessibilityFaceGazeEnabledSentinelShowDialog);
if (!show_dialog) {
face_gaze().SetEnabled(false);
// Reset show dialog pref to default after pref is handled.
active_user_prefs_->SetBoolean(
prefs::kAccessibilityFaceGazeEnabledSentinelShowDialog, true);
return;
}
} else if (sentinel_pref ==
prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel) {
window_title_text_id =
IDS_ASH_FACEGAZE_CURSOR_CONTROL_DISABLE_CONFIRMATION_TEXT;
} else if (sentinel_pref ==
prefs::kAccessibilityFaceGazeActionsEnabledSentinel) {
window_title_text_id = IDS_ASH_FACEGAZE_ACTIONS_DISABLE_CONFIRMATION_TEXT;
}
ShowFeatureDisableDialog(
window_title_text_id,
BindOnce(&AccessibilityController::OnFaceGazeDisableDialogClosed,
GetWeakPtr(), sentinel_pref, behavior_pref,
/*dialog_accepted=*/true),
BindOnce(&AccessibilityController::OnFaceGazeDisableDialogClosed,
GetWeakPtr(), sentinel_pref, behavior_pref,
/*dialog_accepted=*/false));
}
void AccessibilityController::OnFaceGazeDisableDialogClosed(
const std::string& sentinel_pref,
const std::string& behavior_pref,
bool dialog_accepted) {
if (dialog_accepted) {
// After confirmation, set behavior pref to false to turn off the feature.
active_user_prefs_->SetBoolean(behavior_pref, false);
} else {
// Ensure sentinel pref is in sync with behavior pref if dialog is
// cancelled.
active_user_prefs_->SetBoolean(sentinel_pref, true);
}
disable_dialog_.reset();
}
void AccessibilityController::UpdateFaceGazeBubble(const std::u16string& text,
bool is_warning) {
if (!facegaze_bubble_controller_ ||

@ -55,6 +55,7 @@ namespace ash {
class AccessibilityConfirmationDialog;
class AccessibilityControllerClient;
class AccessibilityEventRewriter;
class AccessibilityFeatureDisableDialog;
class AccessibilityHighlightController;
class AccessibilityObserver;
enum class AccessibilityPanelState;
@ -644,6 +645,12 @@ class ASH_EXPORT AccessibilityController
std::optional<int> timeout_seconds = std::nullopt);
gfx::Rect GetConfirmationDialogBoundsInScreen();
// Shows a dialog to disable a feature with the given text, and calls the
// relevant callback when the dialog is accepted or cancelled.
void ShowFeatureDisableDialog(int window_title_text_id,
base::OnceClosure on_accept_callback,
base::OnceClosure on_cancel_callback);
void PreviewFlashNotification() const;
// SessionObserver:
@ -679,6 +686,9 @@ class ASH_EXPORT AccessibilityController
AccessibilityConfirmationDialog* GetConfirmationDialogForTest() {
return confirmation_dialog_.get();
}
AccessibilityFeatureDisableDialog* GetFeatureDisableDialogForTest() {
return disable_dialog_.get();
}
bool enable_chromevox_volume_slide_gesture() {
return enable_chromevox_volume_slide_gesture_;
@ -718,6 +728,9 @@ class ASH_EXPORT AccessibilityController
void AddShowConfirmationDialogCallbackForTesting(
base::RepeatingCallback<void()> callback);
void AddFeatureDisableDialogCallbackForTesting(
base::RepeatingCallback<void()> callback);
bool VerifyFeaturesDataForTesting();
SelectToSpeakEventHandler* GetSelectToSpeakEventHandlerForTesting() const {
@ -813,6 +826,12 @@ class ASH_EXPORT AccessibilityController
void OnDisableTouchpadDialogDismissed();
void ExternalDeviceConnected();
void OnFaceGazeSentinelChanged(const std::string& sentinel_pref,
const std::string& behavior_pref);
void OnFaceGazeDisableDialogClosed(const std::string& sentinel_pref,
const std::string& behavior_pref,
bool dialog_accepted);
void RecordSelectToSpeakSpeechDuration(SelectToSpeakState old_state,
SelectToSpeakState new_state);
@ -912,6 +931,11 @@ class ASH_EXPORT AccessibilityController
base::RepeatingCallback<void()>
show_confirmation_dialog_callback_for_testing_;
// The current AccessibilityFeatureDisableDialog, if one exists.
base::WeakPtr<AccessibilityFeatureDisableDialog> disable_dialog_;
base::RepeatingCallback<void()> show_disable_dialog_callback_for_testing_;
base::Time select_to_speak_speech_start_time_;
base::ScopedObservation<InputDeviceSettingsController,

@ -342,6 +342,14 @@ TEST_F(AccessibilityControllerTest, PrefsAreRegistered) {
prefs()->FindPreference(prefs::kAccessibilityFaceGazePrecisionClick));
EXPECT_TRUE(prefs()->FindPreference(
prefs::kAccessibilityFaceGazePrecisionClickSpeedFactor));
EXPECT_TRUE(
prefs()->FindPreference(prefs::kAccessibilityFaceGazeEnabledSentinel));
EXPECT_TRUE(prefs()->FindPreference(
prefs::kAccessibilityFaceGazeEnabledSentinelShowDialog));
EXPECT_TRUE(prefs()->FindPreference(
prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel));
EXPECT_TRUE(prefs()->FindPreference(
prefs::kAccessibilityFaceGazeActionsEnabledSentinel));
EXPECT_TRUE(prefs()->FindPreference(prefs::kAccessibilityCaretBlinkInterval));
EXPECT_TRUE(
prefs()->FindPreference(prefs::kAccessibilityFlashNotificationsEnabled));

@ -1512,6 +1512,17 @@ Style notes:
You can also use the keyboard shortcut. First, highlight text, then press <ph name="modifier">$1<ex>launcher</ex></ph> + s.
</message>
<!-- FaceGaze dialog strings -->
<message name="IDS_ASH_FACEGAZE_DISABLE_CONFIRMATION_TEXT" desc="The text for the modal dialog shown when the user disables FaceGaze, to confirm they meant to disable the feature.">
Are you sure you want to turn off face control?
</message>
<message name="IDS_ASH_FACEGAZE_CURSOR_CONTROL_DISABLE_CONFIRMATION_TEXT" desc="The text for the modal dialog shown when the user disables cursor control in FaceGaze, to confirm they meant to disable the feature." translateable="false">
Are you sure you want to turn off mouse cursor control?
</message>
<message name="IDS_ASH_FACEGAZE_ACTIONS_DISABLE_CONFIRMATION_TEXT" desc="The text for the modal dialog shown when the user disables performing actions using facial gestures in FaceGaze, to confirm they meant to disable the feature." translateable="false">
Are you sure you want to turn off facial gestures?
</message>
<!-- Accessibility nudges -->
<message name="IDS_ASH_ACCESSIBILITY_NUDGE_DICTATION_NO_FOCUSED_TEXT_FIELD" desc="Displayed in a nudge when Dictation is stopped automatically because there is no focused text field.">
Go to a text field to use Dictation

@ -0,0 +1 @@
2ef8fbb30486b5eb3b1bee216347beaa3de2040c

@ -917,6 +917,49 @@ inline constexpr char kAccessibilityFaceGazePrecisionClick[] =
// should be dampened by during a precision click.
inline constexpr char kAccessibilityFaceGazePrecisionClickSpeedFactor[] =
"settings.a11y.face_gaze.precision_click_speed_factor";
// A boolean pref which indicates when a request has been made to change the
// FaceGaze enabled state. This pref acts a sentinel for the requested state.
// The feature uses this pref to determine whether FaceGaze should be 1) enabled
// 2) disabled, or 3) a dialog needs to be shown to confirm whether the user
// wants to disable the feature. Using a separate sentinel pref to store the
// requested state here ensures the kAccessibilityFaceGazeEnabled pref always
// accurately reflects the feature state, specifically in the case where the
// sentinel pref is set to false and the behavior pref must remain true until
// the confirmation dialog is accepted or cancelled. This is to ensure the user
// can interact with the dialog with FaceGaze as expected. In all other
// scenarios, the behavior pref and sentinel pref are kept in sync.
inline constexpr char kAccessibilityFaceGazeEnabledSentinel[] =
"settings.a11y.face_gaze.enabled_sentinel";
// A boolean pref which indicates whether the confirmation dialog should be
// shown when kAccessibilityFaceGazeEnabledSentinel is set to false.
inline constexpr char kAccessibilityFaceGazeEnabledSentinelShowDialog[] =
"settings.a11y.face_gaze.enabled_sentinel_show_dialog";
// A boolean pref which indicates the requested enabled state for FaceGaze
// cursor control. This pref acts as a sentinel for the requested cursor control
// state. The feature uses this pref to determine whether cursor control should
// be 1) enabled or 2) a dialog needs to be shown to confirm whether the user
// wants to disable the feature. Using a separate sentinel pref to store the
// requested state here ensures the kAccessibilityFaceGazeCursorControlEnabled
// pref always accurately reflects the feature state, specifically in the case
// where the sentinel pref is set to false and the behavior pref must remain
// true until the confirmation dialog is accepted or cancelled. This is to
// ensure the user can interact with the dialog with FaceGaze as expected. In
// all other scenarios, the behavior pref and sentinel pref are kept in sync.
inline constexpr char kAccessibilityFaceGazeCursorControlEnabledSentinel[] =
"settings.a11y.face_gaze.cursor_control_enabled_sentinel";
// A boolean pref which indicates the requested enabled state for FaceGaze
// actions. This pref acts as a sentinel for the requested actions
// state. The feature uses this pref to determine whether actions should
// be 1) enabled or 2) a dialog needs to be shown to confirm whether the user
// wants to disable the feature. Using a separate sentinel pref to store the
// requested state here ensures the kAccessibilityFaceGazeActionsEnabled pref
// always accurately reflects the feature state, specifically in the case where
// the sentinel pref is set to false and the behavior pref must remain true
// until the confirmation dialog is accepted or cancelled. This is to ensure the
// user can interact with the dialog with FaceGaze as expected. In all other
// scenarios, the behavior pref and sentinel pref are kept in sync.
inline constexpr char kAccessibilityFaceGazeActionsEnabledSentinel[] =
"settings.a11y.face_gaze.actions_enabled_sentinel";
// A boolean pref which determines whether the accessibility menu shows
// regardless of the state of a11y features.

@ -9,6 +9,7 @@
#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/run_loop.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
@ -683,7 +684,7 @@ IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, DefaultBehavior) {
GetPrefs()->GetDict(prefs::kAccessibilityFaceGazeGesturesToConfidence);
ASSERT_EQ(gestures_to_macros.size(), 2u);
ASSERT_EQ(gestures_to_confidences.size(), 2u);
ASSERT_EQ(/* MOUTH_SMILE */ 35,
ASSERT_EQ(/* MOUSE_CLICK_LEFT */ 35,
gestures_to_macros.FindInt(
FaceGazeTestUtils::ToString(FaceGazeGesture::MOUTH_SMILE)));
ASSERT_EQ(/* SCROLL */ 50,
@ -695,4 +696,341 @@ IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, DefaultBehavior) {
FaceGazeTestUtils::ToString(FaceGazeGesture::JAW_OPEN)));
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, EnableNoDialog) {
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 enable the feature without showing
// the feature disable dialog.
prefs->SetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel, true);
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
}
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_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
// Setting sentinel value to false should show the feature disable dialog and
// leave the behavior pref unchanged.
prefs->SetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel, false);
dialog_waiter.Run();
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_FALSE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel));
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 behavior and sentinel prefs are in sync.
ASSERT_FALSE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_FALSE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel));
}
IN_PROC_BROWSER_TEST_F(FaceGazeIntegrationTest, DisableDialogNoShow) {
auto* controller = ash::Shell::Get()->accessibility_controller();
auto* prefs = GetPrefs();
// Enabling FaceGaze should not show the feature disable dialog.
utils()->EnableFaceGaze(Config().Default());
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
base::RunLoop pref_waiter;
PrefChangeRegistrar change_observer;
change_observer.Init(prefs);
change_observer.Add(prefs::kAccessibilityFaceGazeEnabled,
pref_waiter.QuitClosure());
// Setting show dialog value to false should allow the feature to be set to
// false when the sentinel is set to false.
prefs->SetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinelShowDialog,
false);
prefs->SetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel, false);
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
pref_waiter.Run();
// Assert behavior and sentinel prefs are in sync.
ASSERT_FALSE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabled));
ASSERT_FALSE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel));
}
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_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel));
ASSERT_EQ(nullptr, controller->GetFeatureDisableDialogForTest());
// Setting sentinel value to false should show the feature disable dialog and
// leave the behavior pref unchanged.
prefs->SetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel, false);
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 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::kAccessibilityFaceGazeEnabled));
ASSERT_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel));
}
// 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_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel));
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_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel));
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_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel));
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_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel));
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_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel));
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_TRUE(prefs->GetBoolean(prefs::kAccessibilityFaceGazeEnabledSentinel));
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));
}
} // namespace ash

@ -779,6 +779,15 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetAllowlistedKeys() {
settings_api::PrefType::kBoolean;
(*s_allowlist)[ash::prefs::kAccessibilityFaceGazePrecisionClickSpeedFactor] =
settings_api::PrefType::kNumber;
(*s_allowlist)[ash::prefs::kAccessibilityFaceGazeEnabledSentinel] =
settings_api::PrefType::kBoolean;
(*s_allowlist)[ash::prefs::kAccessibilityFaceGazeEnabledSentinelShowDialog] =
settings_api::PrefType::kBoolean;
(*s_allowlist)
[ash::prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel] =
settings_api::PrefType::kBoolean;
(*s_allowlist)[ash::prefs::kAccessibilityFaceGazeActionsEnabledSentinel] =
settings_api::PrefType::kBoolean;
(*s_allowlist)[ash::prefs::kAccessibilityCaretBlinkInterval] =
settings_api::PrefType::kNumber;
(*s_allowlist)[ash::prefs::kAccessibilityDisableTrackpadEnabled] =

@ -90,6 +90,10 @@ const char* const kPersistentPrefNames[] = {
ash::prefs::kAccessibilityFaceGazeVelocityThreshold,
ash::prefs::kAccessibilityFaceGazePrecisionClick,
ash::prefs::kAccessibilityFaceGazePrecisionClickSpeedFactor,
ash::prefs::kAccessibilityFaceGazeEnabledSentinel,
ash::prefs::kAccessibilityFaceGazeEnabledSentinelShowDialog,
ash::prefs::kAccessibilityFaceGazeCursorControlEnabledSentinel,
ash::prefs::kAccessibilityFaceGazeActionsEnabledSentinel,
ash::prefs::kAccessibilityFlashNotificationsEnabled,
ash::prefs::kAccessibilityFlashNotificationsColor,
ash::prefs::kAccessibilityHighContrastEnabled,

@ -44,7 +44,7 @@
<settings-toggle-button
id="faceGazeActionsEnabledButton"
label="$i18n{faceGazeActionsEnabledLabel}"
pref="{{prefs.settings.a11y.face_gaze.actions_enabled}}"
pref="{{prefs.settings.a11y.face_gaze.actions_enabled_sentinel}}"
disabled="[[disabled]]">
</settings-toggle-button>
<dom-repeat id="faceGazeActionsCommandPairs"

@ -35,7 +35,7 @@
<settings-toggle-button
id="faceGazeCursorControlEnabledButton"
label="$i18n{faceGazeCursorControlEnabledLabel}"
pref="{{prefs.settings.a11y.face_gaze.cursor_control_enabled}}">
pref="{{prefs.settings.a11y.face_gaze.cursor_control_enabled_sentinel}}">
</settings-toggle-button>
<div class="settings-box short">
$i18n{faceGazeCursorSpeedSectionName}

@ -19,7 +19,7 @@
<settings-toggle-button
id="faceGazeToggle"
label="[[toggleLabel_]]"
pref="{{prefs.settings.a11y.face_gaze.enabled}}"
pref="{{prefs.settings.a11y.face_gaze.enabled_sentinel}}"
deep-link-focus-id$="[[Setting.kFaceGaze]]">
</settings-toggle-button>
</settings-card>

@ -46,7 +46,7 @@ export class SettingsFaceGazeSubpageElement extends
toggleLabel_: {
type: String,
computed:
'getToggleLabel_(prefs.settings.a11y.face_gaze.enabled.value)',
'getToggleLabel_(prefs.settings.a11y.face_gaze.enabled_sentinel.value)',
},
supportedSettingIds: {
@ -59,7 +59,7 @@ export class SettingsFaceGazeSubpageElement extends
}
private getToggleLabel_(): string {
return this.getPref('settings.a11y.face_gaze.enabled').value ?
return this.getPref('settings.a11y.face_gaze.enabled_sentinel').value ?
this.i18n('deviceOn') :
this.i18n('deviceOff');
}

@ -11,9 +11,15 @@ export enum PrefNames {
ACCELERATOR_DIALOG_HAS_BEEN_ACCEPTED =
'settings.a11y.face_gaze.accelerator_dialog_has_been_accepted',
ACTIONS_ENABLED = 'settings.a11y.face_gaze.actions_enabled',
ACTIONS_ENABLED_SENTINEL = 'settings.a11y.face_gaze.actions_enabled_sentinel',
CURSOR_CONTROL_ENABLED = 'settings.a11y.face_gaze.cursor_control_enabled',
CURSOR_CONTROL_ENABLED_SENTINEL =
'settings.a11y.face_gaze.cursor_control_enabled_sentinel',
CURSOR_USE_ACCELERATION = 'settings.a11y.face_gaze.cursor_use_acceleration',
FACE_GAZE_ENABLED = 'settings.a11y.face_gaze.enabled',
FACE_GAZE_ENABLED_SENTINEL = 'settings.a11y.face_gaze.enabled_sentinel',
FACE_GAZE_ENABLED_SENTINEL_SHOW_DIALOG =
'settings.a11y.face_gaze.enabled_sentinel_show_dialog',
GESTURE_TO_CONFIDENCE = 'settings.a11y.face_gaze.gestures_to_confidence',
GESTURE_TO_KEY_COMBO = 'settings.a11y.face_gaze.gestures_to_key_combos',
GESTURE_TO_MACRO = 'settings.a11y.face_gaze.gestures_to_macros',

@ -115,8 +115,13 @@ export class FaceGaze {
chrome.settingsPrivate.setPref(
PrefNames.ACCELERATOR_DIALOG_HAS_BEEN_ACCEPTED, accepted);
if (!accepted) {
// If the dialog was rejected, then disable the FaceGaze feature.
chrome.settingsPrivate.setPref(PrefNames.FACE_GAZE_ENABLED, false);
// If the dialog was rejected, then disable the FaceGaze feature and do
// not show the confirmation dialog for disabling.
chrome.settingsPrivate.setPref(
PrefNames.FACE_GAZE_ENABLED_SENTINEL_SHOW_DIALOG, false, undefined,
() => chrome.settingsPrivate.setPref(
PrefNames.FACE_GAZE_ENABLED_SENTINEL, false));
return;
}

@ -88,7 +88,10 @@ export class WebCamFaceLandmarker {
`Couldn't create FaceLandmarker because FaceGaze assets couldn't be
installed.`);
chrome.settingsPrivate.setPref(PrefNames.FACE_GAZE_ENABLED, false);
chrome.settingsPrivate.setPref(
PrefNames.FACE_GAZE_ENABLED_SENTINEL_SHOW_DIALOG, false, undefined,
() => chrome.settingsPrivate.setPref(
PrefNames.FACE_GAZE_ENABLED_SENTINEL, false));
return;
}

@ -149,8 +149,8 @@ suite('<facegaze-actions-card>', () => {
test('actions enabled button syncs to pref', async () => {
await initPage();
assertTrue(faceGazeActionsCard.prefs.settings.a11y.face_gaze.actions_enabled
.value);
assertTrue(faceGazeActionsCard.prefs.settings.a11y.face_gaze
.actions_enabled_sentinel.value);
const button = faceGazeActionsCard.shadowRoot!
.querySelector<SettingsToggleButtonElement>(
@ -164,7 +164,7 @@ suite('<facegaze-actions-card>', () => {
assertFalse(button.checked);
assertFalse(faceGazeActionsCard.prefs.settings.a11y.face_gaze
.actions_enabled.value);
.actions_enabled_sentinel.value);
});
test('actions disables controls if feature is disabled', async () => {

@ -77,7 +77,7 @@ suite('<facegaze-cursor-card>', () => {
const prefs = faceGazeCursorCard.prefs.settings.a11y.face_gaze;
assertTrue(prefs.cursor_control_enabled.value);
assertTrue(prefs.cursor_control_enabled_sentinel.value);
const button = faceGazeCursorCard.shadowRoot!
.querySelector<SettingsToggleButtonElement>(
@ -90,7 +90,7 @@ suite('<facegaze-cursor-card>', () => {
flush();
assertFalse(button.checked);
assertFalse(prefs.cursor_control_enabled.value);
assertFalse(prefs.cursor_control_enabled_sentinel.value);
});
test(

@ -71,10 +71,12 @@ suite('<settings-facegaze-subpage>', () => {
test('toggle button reflects pref value', async () => {
await initPage();
faceGazeSubpage.set('prefs.settings.a11y.face_gaze.enabled.value', true);
faceGazeSubpage.set(
'prefs.settings.a11y.face_gaze.enabled_sentinel.value', true);
await flushTasks();
assertTrue(faceGazeSubpage.prefs.settings.a11y.face_gaze.enabled.value);
assertTrue(
faceGazeSubpage.prefs.settings.a11y.face_gaze.enabled_sentinel.value);
const toggle = getToggleButton();
assertTrue(!!toggle);
@ -86,7 +88,8 @@ suite('<settings-facegaze-subpage>', () => {
test('clicking toggle button updates pref value', async () => {
await initPage();
assertFalse(faceGazeSubpage.prefs.settings.a11y.face_gaze.enabled.value);
assertFalse(
faceGazeSubpage.prefs.settings.a11y.face_gaze.enabled_sentinel.value);
const toggle = getToggleButton();
assertTrue(!!toggle);
@ -98,7 +101,8 @@ suite('<settings-facegaze-subpage>', () => {
await flushTasks();
assertTrue(toggle.checked);
assertTrue(faceGazeSubpage.prefs.settings.a11y.face_gaze.enabled.value);
assertTrue(
faceGazeSubpage.prefs.settings.a11y.face_gaze.enabled_sentinel.value);
assertEquals('On', toggle.label);
});
});