0

FaceGaze: Add pinned notification

This CL adds a pinned notification for FaceGaze, which can be used to
quickly toggle the feature off.

Screenshot: https://drive.google.com/file/d/1nUa-wyxyqDeNLshiYDA1cYoIvUTTYUqX/view?usp=sharing&resourcekey=0-35GmVeAWeR46vjPAI_tUWg

Bug: 404622220
Change-Id: I16022c36ffcbc37c98d180ac4bd06f3d5f0f6b48
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6377204
Reviewed-by: Ahmed Mehfooz <amehfooz@chromium.org>
Commit-Queue: Akihiro Ota <akihiroota@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1437514}
This commit is contained in:
Akihiro Ota
2025-03-25 08:29:48 -07:00
committed by Chromium LUCI CQ
parent b5c5d454ce
commit 66bc8a6932
8 changed files with 111 additions and 17 deletions

@ -233,6 +233,8 @@ const FeatureDialogData kFeatureDialogs[] = {
{FeatureType::kHighContrast,
prefs::kHighContrastAcceleratorDialogHasBeenAccepted}};
constexpr char kFaceGazeActiveNotificationId[] =
"facegaze_active_notification_id";
constexpr char kNotificationId[] = "chrome://settings/accessibility";
constexpr char kNotifierAccessibility[] = "ash.accessibility";
constexpr char kDictationLanguageUpgradedNudgeId[] =
@ -460,6 +462,8 @@ const gfx::VectorIcon& GetNotificationIcon(A11yNotificationType type) {
case A11yNotificationType::kDicationOnlyPumpkinDownloaded:
case A11yNotificationType::kDictationOnlySodaDownloaded:
return kDictationMenuIcon;
case A11yNotificationType::kFaceGazeActive:
return kFacegazeIcon;
default:
return kNotificationChromevoxIcon;
}
@ -468,10 +472,11 @@ const gfx::VectorIcon& GetNotificationIcon(A11yNotificationType type) {
void ShowAccessibilityNotification(
const AccessibilityController::A11yNotificationWrapper& wrapper) {
A11yNotificationType type = wrapper.type;
std::string notification_id = wrapper.notification_id;
const auto& replacements = wrapper.replacements;
message_center::MessageCenter* message_center =
message_center::MessageCenter::Get();
message_center->RemoveNotification(kNotificationId, false /* by_user */);
message_center->RemoveNotification(notification_id, false /* by_user */);
if (type == A11yNotificationType::kNone) {
return;
@ -546,6 +551,13 @@ void ShowAccessibilityNotification(
pinned = false;
// Use CRITICAL_WARNING to force the notification color to red.
warning = message_center::SystemNotificationWarningLevel::CRITICAL_WARNING;
} else if (type == A11yNotificationType::kFaceGazeActive) {
title =
l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_FACEGAZE_ACTIVE_TITLE);
catalog_name = NotificationCatalogName::kFaceGazeActive;
options.pinned = true;
options.buttons.emplace_back(
l10n_util::GetStringUTF16(IDS_ASH_FACEGAZE_CLOSE_BUTTON_TEXT));
} else if (type == A11yNotificationType::kFaceGazeAssetsDownloaded) {
title = l10n_util::GetStringUTF16(
IDS_ASH_A11Y_FACEGAZE_ASSETS_DOWNLOADED_TITLE);
@ -594,7 +606,7 @@ void ShowAccessibilityNotification(
options.should_make_spoken_feedback_for_popup_updates = false;
std::unique_ptr<message_center::Notification> notification =
ash::CreateSystemNotificationPtr(
message_center::NOTIFICATION_TYPE_SIMPLE, kNotificationId, title,
message_center::NOTIFICATION_TYPE_SIMPLE, notification_id, title,
text, display_source, GURL(),
message_center::NotifierId(
message_center::NotifierType::SYSTEM_COMPONENT,
@ -607,7 +619,8 @@ void ShowAccessibilityNotification(
void RemoveAccessibilityNotification() {
ShowAccessibilityNotification(
AccessibilityController::A11yNotificationWrapper(
A11yNotificationType::kNone, std::vector<std::u16string>()));
A11yNotificationType::kNone, kNotificationId,
std::vector<std::u16string>()));
}
AccessibilityPanelLayoutManager* GetLayoutManager() {
@ -1918,8 +1931,8 @@ void AccessibilityController::SetSpokenFeedbackEnabled(
if (enabled && actual_enabled && notify == A11Y_NOTIFICATION_SHOW) {
type = A11yNotificationType::kSpokenFeedbackEnabled;
}
ShowAccessibilityNotification(
A11yNotificationWrapper(type, std::vector<std::u16string>()));
ShowAccessibilityNotification(A11yNotificationWrapper(
type, kNotificationId, std::vector<std::u16string>()));
}
bool AccessibilityController::IsSpokenFeedbackSettingVisibleInTray() {
@ -1942,6 +1955,15 @@ void AccessibilityController::RequestSelectToSpeakStateChange() {
client_->RequestSelectToSpeakStateChange();
}
void AccessibilityController::OnFaceGazeActiveNotificationClicked(
std::optional<int> button_index) {
if (!button_index) {
return;
}
RequestDisableFaceGaze();
}
void AccessibilityController::OnTouchpadNotificationClicked(
std::optional<int> button_index) {
if (!button_index) {
@ -2443,8 +2465,8 @@ void AccessibilityController::BrailleDisplayStateChanged(bool connected) {
}
NotifyAccessibilityStatusChanged();
ShowAccessibilityNotification(
A11yNotificationWrapper(type, std::vector<std::u16string>()));
ShowAccessibilityNotification(A11yNotificationWrapper(
type, kNotificationId, std::vector<std::u16string>()));
}
void AccessibilityController::SetFocusHighlightRect(
@ -2553,9 +2575,9 @@ void AccessibilityController::OnDisplayTabletStateChanged(
// Show accessibility notification when tablet mode transition is completed.
if (state == display::TabletState::kInTabletMode ||
state == display::TabletState::kInClamshellMode) {
ShowAccessibilityNotification(
A11yNotificationWrapper(A11yNotificationType::kSpokenFeedbackEnabled,
std::vector<std::u16string>()));
ShowAccessibilityNotification(A11yNotificationWrapper(
A11yNotificationType::kSpokenFeedbackEnabled, kNotificationId,
std::vector<std::u16string>()));
}
}
}
@ -3070,6 +3092,19 @@ void AccessibilityController::UpdateFaceGazeFromPrefs() {
active_user_prefs_->SetBoolean(
prefs::kAccessibilityFaceGazeActionsEnabledSentinel, actions_enabled);
}
// Manage the pinned notification.
if (enabled) {
ShowAccessibilityNotification(A11yNotificationWrapper(
A11yNotificationType::kFaceGazeActive, kFaceGazeActiveNotificationId,
std::vector<std::u16string>(),
base::BindRepeating(
&AccessibilityController::OnFaceGazeActiveNotificationClicked,
GetWeakPtr())));
} else {
message_center::MessageCenter::Get()->RemoveNotification(
kFaceGazeActiveNotificationId, /*by_user=*/false);
}
}
void AccessibilityController::UpdateFlashNotificationsFromPrefs() {
@ -3198,7 +3233,8 @@ void AccessibilityController::ShowDisableTouchpadDialog() {
void AccessibilityController::OnDisableTouchpadDialogAccepted() {
confirmation_dialog_.reset();
ShowAccessibilityNotification(A11yNotificationWrapper(
A11yNotificationType::kTouchpadDisabled, std::vector<std::u16string>(),
A11yNotificationType::kTouchpadDisabled, kNotificationId,
std::vector<std::u16string>(),
base::BindRepeating(
&AccessibilityController::OnTouchpadNotificationClicked,
GetWeakPtr())));
@ -3507,7 +3543,7 @@ void AccessibilityController::ActivateSwitchAccess() {
ShowAccessibilityNotification(
A11yNotificationWrapper(A11yNotificationType::kSwitchAccessEnabled,
std::vector<std::u16string>()));
kNotificationId, std::vector<std::u16string>()));
}
void AccessibilityController::DeactivateSwitchAccess() {
@ -3745,8 +3781,9 @@ void AccessibilityController::ShowNotificationForDictation(
break;
}
ShowAccessibilityNotification(A11yNotificationWrapper(
notification_type, std::vector<std::u16string>{display_language}));
ShowAccessibilityNotification(
A11yNotificationWrapper(notification_type, kNotificationId,
std::vector<std::u16string>{display_language}));
}
void AccessibilityController::ShowNotificationForFaceGaze(
@ -3775,21 +3812,26 @@ void AccessibilityController::ShowNotificationForFaceGaze(
active_user_prefs_->SetBoolean(notification_shown_pref, true);
ShowAccessibilityNotification(A11yNotificationWrapper(
notification_type, std::vector<std::u16string>()));
notification_type, kNotificationId, std::vector<std::u16string>()));
}
AccessibilityController::A11yNotificationWrapper::A11yNotificationWrapper() =
default;
AccessibilityController::A11yNotificationWrapper::A11yNotificationWrapper(
A11yNotificationType type_in,
const std::string& notification_id_in,
std::vector<std::u16string> replacements_in)
: type(type_in), replacements(replacements_in) {}
: type(type_in),
notification_id(notification_id_in),
replacements(replacements_in) {}
AccessibilityController::A11yNotificationWrapper::A11yNotificationWrapper(
A11yNotificationType type_in,
const std::string& notification_id_in,
std::vector<std::u16string> replacements_in,
std::optional<base::RepeatingCallback<void(std::optional<int>)>>
callback_in)
: type(type_in),
notification_id(notification_id_in),
replacements(replacements_in),
callback(std::move(callback_in)) {}
AccessibilityController::A11yNotificationWrapper::~A11yNotificationWrapper() =

@ -101,6 +101,8 @@ enum class A11yNotificationType {
kDicationOnlyPumpkinDownloaded,
// Shown when the SODA DLC (but no other DLCs) have downloaded.
kDictationOnlySodaDownloaded,
// Shown when FaceGaze is active.
kFaceGazeActive,
// Shown when the facegaze-assets DLC has successfully downloaded.
kFaceGazeAssetsDownloaded,
// Shown when the facegaze-assets DLC failed to download.
@ -215,9 +217,11 @@ class ASH_EXPORT AccessibilityController
struct A11yNotificationWrapper {
A11yNotificationWrapper();
A11yNotificationWrapper(A11yNotificationType type_in,
const std::string& notification_id_in,
std::vector<std::u16string> replacements_in);
A11yNotificationWrapper(
A11yNotificationType type_in,
const std::string& notification_id_in,
std::vector<std::u16string> replacements_in,
std::optional<base::RepeatingCallback<void(std::optional<int>)>>
callback_in);
@ -225,6 +229,7 @@ class ASH_EXPORT AccessibilityController
A11yNotificationWrapper(const A11yNotificationWrapper&);
A11yNotificationType type = A11yNotificationType::kNone;
std::string notification_id;
std::vector<std::u16string> replacements;
std::optional<base::RepeatingCallback<void(std::optional<int>)>> callback;
};
@ -368,6 +373,7 @@ class ASH_EXPORT AccessibilityController
bool IsTouchpadDisabled();
void OnFaceGazeActiveNotificationClicked(std::optional<int> button_index);
void OnTouchpadNotificationClicked(std::optional<int> button_index);
// Switch access may be disabled in prefs but still running when the disable

@ -37,7 +37,9 @@
#include "ash/test/ash_test_base.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
@ -1941,6 +1943,38 @@ TEST_F(AccessibilityControllerTest, FaceGazeNotifications) {
ASSERT_EQ(1u, MessageCenter::Get()->GetVisibleNotifications().size());
}
TEST_F(AccessibilityControllerTest, ShowNotificationOnFaceGaze) {
// Enabling FaceGaze should show a pinned notification.
controller()->face_gaze().SetEnabled(true);
message_center::NotificationList::Notifications notifications =
MessageCenter::Get()->GetVisibleNotifications();
ASSERT_EQ(1u, notifications.size());
EXPECT_EQ(u"Face control active", (*notifications.begin())->title());
ASSERT_TRUE((*notifications.begin())->pinned());
// Disabling FaceGaze should clear the notification.
controller()->face_gaze().SetEnabled(false);
notifications = MessageCenter::Get()->GetVisibleNotifications();
EXPECT_EQ(0u, notifications.size());
}
TEST_F(AccessibilityControllerTest, ClickNotification) {
// Enabling FaceGaze should show a notification.
controller()->face_gaze().SetEnabled(true);
message_center::NotificationList::Notifications notifications =
MessageCenter::Get()->GetVisibleNotifications();
ASSERT_EQ(1u, notifications.size());
// Clicking the notification will show a confirmation dialog.
base::RunLoop dialog_waiter;
controller()->AddFeatureDisableDialogCallbackForTesting(
base::BindLambdaForTesting([&dialog_waiter]() { dialog_waiter.Quit(); }));
(*notifications.begin())
->delegate()
->Click(/*button_index=*/1, /*reply=*/std::nullopt);
dialog_waiter.Run();
}
namespace {
enum class TestUserLoginType {

@ -1577,6 +1577,9 @@ You can also use the keyboard shortcut. First, highlight text, then press <ph na
<message name="IDS_ASH_FACEGAZE_CLOSE_BUTTON_TEXT" desc="The text for the close button that is shown in the FaceGaze top bar UI.">
Turn off Face control
</message>
<message name="IDS_ASH_STATUS_TRAY_FACEGAZE_ACTIVE_TITLE" desc="The title of the notification shown when FaceGaze is active.">
Face control active
</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.">

@ -0,0 +1 @@
cf871ec1594ee4b7a41f5738f08e90b504b511fd

@ -213,7 +213,8 @@ enum class NotificationCatalogName {
kChromeAppDeprecation = 194,
kDownloadImageFromLobster = 195,
kBocaSpotlightStarted = 196,
kMaxValue = kBocaSpotlightStarted
kFaceGazeActive = 197,
kMaxValue = kFaceGazeActive
};
// A living catalog that registers system nudges.

@ -1610,6 +1610,9 @@ IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
// is successfully downloaded.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, FaceGazeAssetsSucceeded) {
AccessibilityManager::Get()->EnableFaceGaze(true);
// Turning on FaceGaze will add a pinned notification to the message center,
// so clear it for the purposes of this test.
ClearMessageCenter();
InstallFaceGazeAssetsAndWait();
message_center::NotificationList::Notifications notifications =
@ -1625,6 +1628,9 @@ IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, FaceGazeAssetsSucceeded) {
// fails to download.
IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, FaceGazeAssetsFailed) {
AccessibilityManager::Get()->EnableFaceGaze(true);
// Turning on FaceGaze will add a pinned notification to the message center,
// so clear it for the purposes of this test.
ClearMessageCenter();
OnFaceGazeAssetsFailed();
message_center::NotificationList::Notifications notifications =

@ -20850,6 +20850,7 @@ Called by update_net_error_codes.py.-->
<int value="193" label="Scanner action executing"/>
<int value="194" label="Chrome App deprecation"/>
<int value="196" label="Spotlight session start countdown"/>
<int value="197" label="Face Gaze Active"/>
</enum>
<enum name="NQEObservationSource">