1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
6
7 #include <stddef.h>
8 #include <stdint.h>
9
10 #include <memory>
11 #include <utility>
12 #include <vector>
13
14 #include "ash/public/cpp/accelerators.h"
15 #include "ash/public/cpp/accessibility_controller.h"
16 #include "ash/public/cpp/accessibility_controller_enums.h"
17 #include "ash/public/cpp/accessibility_focus_ring_controller.h"
18 #include "ash/public/cpp/accessibility_focus_ring_info.h"
19 #include "ash/public/cpp/ash_pref_names.h"
20 #include "ash/root_window_controller.h"
21 #include "ash/shell.h"
22 #include "ash/sticky_keys/sticky_keys_controller.h"
23 #include "base/bind.h"
24 #include "base/callback.h"
25 #include "base/callback_helpers.h"
26 #include "base/command_line.h"
27 #include "base/memory/ptr_util.h"
28 #include "base/memory/singleton.h"
29 #include "base/metrics/histogram_functions.h"
30 #include "base/path_service.h"
31 #include "base/strings/string_piece.h"
32 #include "base/strings/string_split.h"
33 #include "base/strings/string_util.h"
34 #include "base/strings/stringprintf.h"
35 #include "base/values.h"
36 #include "chrome/browser/accessibility/accessibility_extension_api.h"
37 #include "chrome/browser/browser_process.h"
38 #include "chrome/browser/chrome_notification_types.h"
39 #include "chrome/browser/chromeos/accessibility/accessibility_extension_loader.h"
40 #include "chrome/browser/chromeos/accessibility/dictation_chromeos.h"
41 #include "chrome/browser/chromeos/accessibility/magnification_manager.h"
42 #include "chrome/browser/chromeos/accessibility/select_to_speak_event_handler_delegate.h"
43 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
44 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
45 #include "chrome/browser/chromeos/profiles/profile_helper.h"
46 #include "chrome/browser/extensions/api/braille_display_private/stub_braille_controller.h"
47 #include "chrome/browser/extensions/extension_service.h"
48 #include "chrome/browser/profiles/profile_manager.h"
49 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
50 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
51 #include "chrome/browser/ui/singleton_tabs.h"
52 #include "chrome/common/chrome_paths.h"
53 #include "chrome/common/extensions/api/accessibility_private.h"
54 #include "chrome/common/extensions/extension_constants.h"
55 #include "chrome/common/pref_names.h"
56 #include "chrome/common/url_constants.h"
57 #include "chrome/grit/browser_resources.h"
58 #include "chromeos/audio/chromeos_sounds.h"
59 #include "chromeos/constants/chromeos_switches.h"
60 #include "chromeos/dbus/power/power_manager_client.h"
61 #include "chromeos/dbus/upstart/upstart_client.h"
62 #include "components/language/core/browser/pref_names.h"
63 #include "components/prefs/pref_member.h"
64 #include "components/prefs/pref_service.h"
65 #include "components/user_manager/known_user.h"
66 #include "content/public/browser/browser_accessibility_state.h"
67 #include "content/public/browser/browser_task_traits.h"
68 #include "content/public/browser/browser_thread.h"
69 #include "content/public/browser/focused_node_details.h"
70 #include "content/public/browser/media_session_service.h"
71 #include "content/public/browser/notification_details.h"
72 #include "content/public/browser/notification_service.h"
73 #include "content/public/browser/notification_source.h"
74 #include "content/public/browser/tts_controller.h"
75 #include "content/public/browser/web_ui.h"
76 #include "content/public/common/content_switches.h"
77 #include "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_delegate.h"
78 #include "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.h"
79 #include "extensions/common/constants.h"
80 #include "extensions/common/extension.h"
81 #include "extensions/common/extension_messages.h"
82 #include "extensions/common/extension_resource.h"
83 #include "extensions/common/host_id.h"
84 #include "services/audio/public/cpp/sounds/sounds_manager.h"
85 #include "ui/accessibility/accessibility_switches.h"
86 #include "ui/accessibility/ax_enum_util.h"
87 #include "ui/accessibility/ax_enums.mojom.h"
88 #include "ui/base/ime/chromeos/extension_ime_util.h"
89 #include "ui/base/resource/resource_bundle.h"
90 #include "ui/views/widget/widget.h"
91 #include "ui/views/widget/widget_observer.h"
92 #include "url/gurl.h"
93
94 using extensions::api::braille_display_private::BrailleController;
95 using extensions::api::braille_display_private::DisplayState;
96 using extensions::api::braille_display_private::KeyEvent;
97 using extensions::api::braille_display_private::StubBrailleController;
98
99 namespace chromeos {
100
101 namespace {
102
103 // When this flag is set, system sounds will not be played.
104 constexpr char kAshDisableSystemSounds[] = "ash-disable-system-sounds";
105
106 // A key for the spoken feedback enabled boolean state for a known user.
107 const char kUserSpokenFeedbackEnabled[] = "UserSpokenFeedbackEnabled";
108
109 // A key for the startup sound enabled boolean state for a known user.
110 const char kUserStartupSoundEnabled[] = "UserStartupSoundEnabled";
111
112 // A key for the bluetooth braille display for a user.
113 const char kUserBluetoothBrailleDisplayAddress[] =
114 "UserBluetoothBrailleDisplayAddress";
115
116 // The name of the Brltty upstart job.
117 constexpr char kBrlttyUpstartJobName[] = "brltty";
118
119 static chromeos::AccessibilityManager* g_accessibility_manager = nullptr;
120
121 static BrailleController* g_braille_controller_for_test = nullptr;
122
GetBrailleController()123 BrailleController* GetBrailleController() {
124 if (g_braille_controller_for_test)
125 return g_braille_controller_for_test;
126 // Don't use the real braille controller for tests to avoid automatically
127 // starting ChromeVox which confuses some tests.
128 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
129 if (command_line->HasSwitch(::switches::kTestType))
130 return StubBrailleController::GetInstance();
131 return BrailleController::GetInstance();
132 }
133
134 // Restarts (stops, then starts brltty). If |address| is empty, only stops.
135 // In Upstart, sending an explicit restart command is a no-op if the job isn't
136 // already started. Without knowledge regarding brltty's current job status,
137 // stop followed by start ensures we both stop a started job, and also start
138 // brltty.
RestartBrltty(const std::string & address)139 void RestartBrltty(const std::string& address) {
140 chromeos::UpstartClient* client = chromeos::UpstartClient::Get();
141 client->StopJob(kBrlttyUpstartJobName, {}, base::DoNothing());
142
143 std::vector<std::string> args;
144 if (address.empty())
145 return;
146
147 args.push_back(base::StringPrintf("ADDRESS=%s", address.c_str()));
148 client->StartJob(kBrlttyUpstartJobName, args, base::DoNothing());
149 }
150
VolumeAdjustSoundEnabled()151 bool VolumeAdjustSoundEnabled() {
152 return !base::CommandLine::ForCurrentProcess()->HasSwitch(
153 chromeos::switches::kDisableVolumeAdjustSound);
154 }
155
156 } // namespace
157
158 class AccessibilityPanelWidgetObserver : public views::WidgetObserver {
159 public:
AccessibilityPanelWidgetObserver(views::Widget * widget,base::OnceCallback<void ()> on_destroying)160 AccessibilityPanelWidgetObserver(views::Widget* widget,
161 base::OnceCallback<void()> on_destroying)
162 : widget_(widget), on_destroying_(std::move(on_destroying)) {
163 widget_->AddObserver(this);
164 }
165
~AccessibilityPanelWidgetObserver()166 ~AccessibilityPanelWidgetObserver() override { CHECK(!IsInObserverList()); }
167
OnWidgetClosing(views::Widget * widget)168 void OnWidgetClosing(views::Widget* widget) override {
169 CHECK_EQ(widget_, widget);
170 widget->RemoveObserver(this);
171 std::move(on_destroying_).Run();
172 // |this| should be deleted.
173 }
174
OnWidgetDestroying(views::Widget * widget)175 void OnWidgetDestroying(views::Widget* widget) override {
176 CHECK_EQ(widget_, widget);
177 widget->RemoveObserver(this);
178 std::move(on_destroying_).Run();
179 // |this| should be deleted.
180 }
181
182 private:
183 views::Widget* widget_;
184
185 base::OnceCallback<void()> on_destroying_;
186
187 DISALLOW_COPY_AND_ASSIGN(AccessibilityPanelWidgetObserver);
188 };
189
190 ///////////////////////////////////////////////////////////////////////////////
191 // AccessibilityStatusEventDetails
192
AccessibilityStatusEventDetails(AccessibilityNotificationType notification_type,bool enabled)193 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
194 AccessibilityNotificationType notification_type,
195 bool enabled)
196 : notification_type(notification_type), enabled(enabled) {}
197
198 ///////////////////////////////////////////////////////////////////////////////
199 //
200 // AccessibilityManager
201
202 // static
Initialize()203 void AccessibilityManager::Initialize() {
204 CHECK(g_accessibility_manager == NULL);
205 g_accessibility_manager = new AccessibilityManager();
206 }
207
208 // static
Shutdown()209 void AccessibilityManager::Shutdown() {
210 CHECK(g_accessibility_manager);
211 delete g_accessibility_manager;
212 g_accessibility_manager = NULL;
213 }
214
215 // static
Get()216 AccessibilityManager* AccessibilityManager::Get() {
217 return g_accessibility_manager;
218 }
219
220 // static
ShowAccessibilityHelp(Browser * browser)221 void AccessibilityManager::ShowAccessibilityHelp(Browser* browser) {
222 ShowSingletonTab(browser, GURL(chrome::kChromeAccessibilityHelpURL));
223 }
224
AccessibilityManager()225 AccessibilityManager::AccessibilityManager() {
226 notification_registrar_.Add(this,
227 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
228 content::NotificationService::AllSources());
229 notification_registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
230 content::NotificationService::AllSources());
231 notification_registrar_.Add(this, content::NOTIFICATION_FOCUS_CHANGED_IN_PAGE,
232 content::NotificationService::AllSources());
233 input_method::InputMethodManager::Get()->AddObserver(this);
234 user_manager::UserManager::Get()->AddSessionStateObserver(this);
235
236 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
237 audio::SoundsManager* manager = audio::SoundsManager::Get();
238 manager->Initialize(SOUND_SHUTDOWN,
239 bundle.GetRawDataResource(IDR_SOUND_SHUTDOWN_WAV));
240 manager->Initialize(
241 SOUND_SPOKEN_FEEDBACK_ENABLED,
242 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_ENABLED_WAV));
243 manager->Initialize(
244 SOUND_SPOKEN_FEEDBACK_DISABLED,
245 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_DISABLED_WAV));
246 manager->Initialize(SOUND_PASSTHROUGH,
247 bundle.GetRawDataResource(IDR_SOUND_PASSTHROUGH_WAV));
248 manager->Initialize(SOUND_EXIT_SCREEN,
249 bundle.GetRawDataResource(IDR_SOUND_EXIT_SCREEN_WAV));
250 manager->Initialize(SOUND_ENTER_SCREEN,
251 bundle.GetRawDataResource(IDR_SOUND_ENTER_SCREEN_WAV));
252 manager->Initialize(SOUND_SPOKEN_FEEDBACK_TOGGLE_COUNTDOWN_HIGH,
253 bundle.GetRawDataResource(
254 IDR_SOUND_SPOKEN_FEEDBACK_TOGGLE_COUNTDOWN_HIGH_WAV));
255 manager->Initialize(SOUND_SPOKEN_FEEDBACK_TOGGLE_COUNTDOWN_LOW,
256 bundle.GetRawDataResource(
257 IDR_SOUND_SPOKEN_FEEDBACK_TOGGLE_COUNTDOWN_LOW_WAV));
258 manager->Initialize(SOUND_TOUCH_TYPE,
259 bundle.GetRawDataResource(IDR_SOUND_TOUCH_TYPE_WAV));
260 manager->Initialize(SOUND_DICTATION_END,
261 bundle.GetRawDataResource(IDR_SOUND_DICTATION_END_WAV));
262 manager->Initialize(SOUND_DICTATION_START,
263 bundle.GetRawDataResource(IDR_SOUND_DICTATION_START_WAV));
264 manager->Initialize(
265 SOUND_DICTATION_CANCEL,
266 bundle.GetRawDataResource(IDR_SOUND_DICTATION_CANCEL_WAV));
267 manager->Initialize(SOUND_STARTUP,
268 bundle.GetRawDataResource(IDR_SOUND_STARTUP_WAV));
269
270 if (VolumeAdjustSoundEnabled()) {
271 manager->Initialize(chromeos::SOUND_VOLUME_ADJUST,
272 bundle.GetRawDataResource(IDR_SOUND_VOLUME_ADJUST_WAV));
273 }
274
275 base::FilePath resources_path;
276 if (!base::PathService::Get(chrome::DIR_RESOURCES, &resources_path))
277 NOTREACHED();
278 accessibility_common_extension_loader_ =
279 base::WrapUnique(new AccessibilityExtensionLoader(
280 extension_misc::kAccessibilityCommonExtensionId,
281 resources_path.Append(
282 extension_misc::kAccessibilityCommonExtensionPath),
283 extension_misc::kAccessibilityCommonManifestFilename,
284 extension_misc::kAccessibilityCommonGuestManifestFilename,
285 base::BindRepeating(
286 &AccessibilityManager::PostUnloadAccessibilityCommon,
287 weak_ptr_factory_.GetWeakPtr())));
288 chromevox_loader_ = base::WrapUnique(new AccessibilityExtensionLoader(
289 extension_misc::kChromeVoxExtensionId,
290 resources_path.Append(extension_misc::kChromeVoxExtensionPath),
291 extension_misc::kChromeVoxManifestFilename,
292 extension_misc::kChromeVoxGuestManifestFilename,
293 base::BindRepeating(&AccessibilityManager::PostUnloadChromeVox,
294 weak_ptr_factory_.GetWeakPtr())));
295 select_to_speak_loader_ = base::WrapUnique(new AccessibilityExtensionLoader(
296 extension_misc::kSelectToSpeakExtensionId,
297 resources_path.Append(extension_misc::kSelectToSpeakExtensionPath),
298 extension_misc::kSelectToSpeakManifestFilename,
299 extension_misc::kSelectToSpeakGuestManifestFilename,
300 base::BindRepeating(&AccessibilityManager::PostUnloadSelectToSpeak,
301 weak_ptr_factory_.GetWeakPtr())));
302 switch_access_loader_ = base::WrapUnique(new AccessibilityExtensionLoader(
303 extension_misc::kSwitchAccessExtensionId,
304 resources_path.Append(extension_misc::kSwitchAccessExtensionPath),
305 extension_misc::kSwitchAccessManifestFilename,
306 extension_misc::kSwitchAccessGuestManifestFilename,
307 base::BindRepeating(&AccessibilityManager::PostUnloadSwitchAccess,
308 weak_ptr_factory_.GetWeakPtr())));
309
310 // Connect to the media session service.
311 content::GetMediaSessionService().BindAudioFocusManager(
312 audio_focus_manager_.BindNewPipeAndPassReceiver());
313
314 ash::AcceleratorController::SetVolumeAdjustmentSoundCallback(
315 base::BindRepeating(&AccessibilityManager::PlayVolumeAdjustSound,
316 base::Unretained(this)));
317
318 CrasAudioHandler::Get()->AddAudioObserver(this);
319 }
320
~AccessibilityManager()321 AccessibilityManager::~AccessibilityManager() {
322 CHECK(this == g_accessibility_manager);
323 AccessibilityStatusEventDetails details(ACCESSIBILITY_MANAGER_SHUTDOWN,
324 false);
325 NotifyAccessibilityStatusChanged(details);
326 CrasAudioHandler::Get()->RemoveAudioObserver(this);
327 user_manager::UserManager::Get()->RemoveSessionStateObserver(this);
328 input_method::InputMethodManager::Get()->RemoveObserver(this);
329
330 if (chromevox_panel_) {
331 chromevox_panel_->CloseNow();
332 chromevox_panel_ = nullptr;
333 }
334
335 ash::AcceleratorController::SetVolumeAdjustmentSoundCallback({});
336 }
337
ShouldShowAccessibilityMenu()338 bool AccessibilityManager::ShouldShowAccessibilityMenu() {
339 // If any of the loaded profiles has an accessibility feature turned on - or
340 // enforced to always show the menu - we return true to show the menu.
341 // NOTE: This includes the login screen profile, so if a feature is turned on
342 // at the login screen the menu will show even if the user has no features
343 // enabled inside the session. http://crbug.com/755631
344 std::vector<Profile*> profiles =
345 g_browser_process->profile_manager()->GetLoadedProfiles();
346 for (std::vector<Profile*>::iterator it = profiles.begin();
347 it != profiles.end(); ++it) {
348 PrefService* prefs = (*it)->GetPrefs();
349 if (prefs->GetBoolean(ash::prefs::kAccessibilityStickyKeysEnabled) ||
350 prefs->GetBoolean(ash::prefs::kAccessibilityLargeCursorEnabled) ||
351 prefs->GetBoolean(ash::prefs::kAccessibilitySpokenFeedbackEnabled) ||
352 prefs->GetBoolean(ash::prefs::kAccessibilitySelectToSpeakEnabled) ||
353 prefs->GetBoolean(ash::prefs::kAccessibilitySwitchAccessEnabled) ||
354 prefs->GetBoolean(ash::prefs::kAccessibilityHighContrastEnabled) ||
355 prefs->GetBoolean(ash::prefs::kAccessibilityAutoclickEnabled) ||
356 prefs->GetBoolean(ash::prefs::kShouldAlwaysShowAccessibilityMenu) ||
357 prefs->GetBoolean(ash::prefs::kAccessibilityScreenMagnifierEnabled) ||
358 prefs->GetBoolean(ash::prefs::kAccessibilityVirtualKeyboardEnabled) ||
359 prefs->GetBoolean(ash::prefs::kAccessibilityMonoAudioEnabled) ||
360 prefs->GetBoolean(ash::prefs::kAccessibilityCaretHighlightEnabled) ||
361 prefs->GetBoolean(ash::prefs::kAccessibilityCursorHighlightEnabled) ||
362 prefs->GetBoolean(ash::prefs::kAccessibilityFocusHighlightEnabled) ||
363 prefs->GetBoolean(ash::prefs::kAccessibilityDictationEnabled) ||
364 prefs->GetBoolean(ash::prefs::kDockedMagnifierEnabled)) {
365 return true;
366 }
367 }
368 return false;
369 }
370
UpdateAlwaysShowMenuFromPref()371 void AccessibilityManager::UpdateAlwaysShowMenuFromPref() {
372 if (!profile_)
373 return;
374
375 // Update system tray menu visibility.
376 ash::AccessibilityController::Get()->NotifyAccessibilityStatusChanged();
377 }
378
EnableLargeCursor(bool enabled)379 void AccessibilityManager::EnableLargeCursor(bool enabled) {
380 if (!profile_)
381 return;
382
383 PrefService* pref_service = profile_->GetPrefs();
384 pref_service->SetBoolean(ash::prefs::kAccessibilityLargeCursorEnabled,
385 enabled);
386 pref_service->CommitPendingWrite();
387 }
388
OnLargeCursorChanged()389 void AccessibilityManager::OnLargeCursorChanged() {
390 AccessibilityStatusEventDetails details(ACCESSIBILITY_TOGGLE_LARGE_CURSOR,
391 IsLargeCursorEnabled());
392 NotifyAccessibilityStatusChanged(details);
393 }
394
IsLargeCursorEnabled() const395 bool AccessibilityManager::IsLargeCursorEnabled() const {
396 return profile_ && profile_->GetPrefs()->GetBoolean(
397 ash::prefs::kAccessibilityLargeCursorEnabled);
398 }
399
EnableStickyKeys(bool enabled)400 void AccessibilityManager::EnableStickyKeys(bool enabled) {
401 if (!profile_)
402 return;
403 PrefService* pref_service = profile_->GetPrefs();
404 pref_service->SetBoolean(ash::prefs::kAccessibilityStickyKeysEnabled,
405 enabled);
406 pref_service->CommitPendingWrite();
407 }
408
IsStickyKeysEnabled() const409 bool AccessibilityManager::IsStickyKeysEnabled() const {
410 return profile_ && profile_->GetPrefs()->GetBoolean(
411 ash::prefs::kAccessibilityStickyKeysEnabled);
412 }
413
OnStickyKeysChanged()414 void AccessibilityManager::OnStickyKeysChanged() {
415 AccessibilityStatusEventDetails details(ACCESSIBILITY_TOGGLE_STICKY_KEYS,
416 IsStickyKeysEnabled());
417 NotifyAccessibilityStatusChanged(details);
418 }
419
EnableSpokenFeedback(bool enabled)420 void AccessibilityManager::EnableSpokenFeedback(bool enabled) {
421 if (!profile_)
422 return;
423
424 PrefService* pref_service = profile_->GetPrefs();
425 pref_service->SetBoolean(ash::prefs::kAccessibilitySpokenFeedbackEnabled,
426 enabled);
427 pref_service->CommitPendingWrite();
428 }
429
OnSpokenFeedbackChanged()430 void AccessibilityManager::OnSpokenFeedbackChanged() {
431 if (!profile_)
432 return;
433
434 const bool enabled = profile_->GetPrefs()->GetBoolean(
435 ash::prefs::kAccessibilitySpokenFeedbackEnabled);
436
437 if (!chromeos::ProfileHelper::IsSigninProfile(profile_) &&
438 !chromeos::ProfileHelper::IsLockScreenAppProfile(profile_)) {
439 user_manager::known_user::SetBooleanPref(
440 multi_user_util::GetAccountIdFromProfile(profile_),
441 kUserSpokenFeedbackEnabled, enabled);
442 }
443
444 if (enabled) {
445 chromevox_loader_->SetProfile(
446 profile_,
447 base::BindRepeating(&AccessibilityManager::PostSwitchChromeVoxProfile,
448 weak_ptr_factory_.GetWeakPtr()));
449 }
450
451 if (spoken_feedback_enabled_ == enabled)
452 return;
453
454 spoken_feedback_enabled_ = enabled;
455
456 AccessibilityStatusEventDetails details(ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK,
457 enabled);
458 NotifyAccessibilityStatusChanged(details);
459
460 if (enabled) {
461 chromevox_loader_->Load(
462 profile_, base::BindRepeating(&AccessibilityManager::PostLoadChromeVox,
463 weak_ptr_factory_.GetWeakPtr()));
464 } else {
465 chromevox_loader_->Unload();
466 }
467 UpdateBrailleImeState();
468 }
469
IsSpokenFeedbackEnabled() const470 bool AccessibilityManager::IsSpokenFeedbackEnabled() const {
471 return profile_ && profile_->GetPrefs()->GetBoolean(
472 ash::prefs::kAccessibilitySpokenFeedbackEnabled);
473 }
474
EnableHighContrast(bool enabled)475 void AccessibilityManager::EnableHighContrast(bool enabled) {
476 if (!profile_)
477 return;
478
479 PrefService* pref_service = profile_->GetPrefs();
480 pref_service->SetBoolean(ash::prefs::kAccessibilityHighContrastEnabled,
481 enabled);
482 pref_service->CommitPendingWrite();
483 }
484
IsHighContrastEnabled() const485 bool AccessibilityManager::IsHighContrastEnabled() const {
486 return profile_ && profile_->GetPrefs()->GetBoolean(
487 ash::prefs::kAccessibilityHighContrastEnabled);
488 }
489
OnHighContrastChanged()490 void AccessibilityManager::OnHighContrastChanged() {
491 AccessibilityStatusEventDetails details(
492 ACCESSIBILITY_TOGGLE_HIGH_CONTRAST_MODE, IsHighContrastEnabled());
493 NotifyAccessibilityStatusChanged(details);
494 }
495
OnLocaleChanged()496 void AccessibilityManager::OnLocaleChanged() {
497 if (!profile_)
498 return;
499
500 if (!IsSpokenFeedbackEnabled())
501 return;
502
503 // If the system locale changes and spoken feedback is enabled,
504 // reload ChromeVox so that it switches its internal translations
505 // to the new language.
506 EnableSpokenFeedback(false);
507 EnableSpokenFeedback(true);
508 }
509
OnViewFocusedInArc(const gfx::Rect & bounds_in_screen,bool is_editable)510 void AccessibilityManager::OnViewFocusedInArc(const gfx::Rect& bounds_in_screen,
511 bool is_editable) {
512 ash::AccessibilityController::Get()->SetFocusHighlightRect(bounds_in_screen);
513
514 MagnificationManager* magnification_manager = MagnificationManager::Get();
515 if (magnification_manager)
516 magnification_manager->HandleFocusedRectChangedIfEnabled(bounds_in_screen,
517 is_editable);
518 }
519
PlayEarcon(int sound_key,PlaySoundOption option)520 bool AccessibilityManager::PlayEarcon(int sound_key, PlaySoundOption option) {
521 DCHECK(sound_key < chromeos::SOUND_COUNT);
522 base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
523 if (cl->HasSwitch(kAshDisableSystemSounds))
524 return false;
525 if (option == PlaySoundOption::ONLY_IF_SPOKEN_FEEDBACK_ENABLED &&
526 !IsSpokenFeedbackEnabled()) {
527 return false;
528 }
529 return audio::SoundsManager::Get()->Play(sound_key);
530 }
531
OnTwoFingerTouchStart()532 void AccessibilityManager::OnTwoFingerTouchStart() {
533 if (!profile_)
534 return;
535
536 extensions::EventRouter* event_router =
537 extensions::EventRouter::Get(profile_);
538
539 auto event_args = std::make_unique<base::ListValue>();
540 auto event = std::make_unique<extensions::Event>(
541 extensions::events::ACCESSIBILITY_PRIVATE_ON_TWO_FINGER_TOUCH_START,
542 extensions::api::accessibility_private::OnTwoFingerTouchStart::kEventName,
543 std::move(event_args));
544 event_router->BroadcastEvent(std::move(event));
545 }
546
OnTwoFingerTouchStop()547 void AccessibilityManager::OnTwoFingerTouchStop() {
548 if (!profile_)
549 return;
550
551 extensions::EventRouter* event_router =
552 extensions::EventRouter::Get(profile_);
553
554 auto event_args = std::make_unique<base::ListValue>();
555 auto event = std::make_unique<extensions::Event>(
556 extensions::events::ACCESSIBILITY_PRIVATE_ON_TWO_FINGER_TOUCH_STOP,
557 extensions::api::accessibility_private::OnTwoFingerTouchStop::kEventName,
558 std::move(event_args));
559 event_router->BroadcastEvent(std::move(event));
560 }
561
ShouldToggleSpokenFeedbackViaTouch()562 bool AccessibilityManager::ShouldToggleSpokenFeedbackViaTouch() {
563 return false;
564 }
565
PlaySpokenFeedbackToggleCountdown(int tick_count)566 bool AccessibilityManager::PlaySpokenFeedbackToggleCountdown(int tick_count) {
567 return audio::SoundsManager::Get()->Play(
568 tick_count % 2 ? SOUND_SPOKEN_FEEDBACK_TOGGLE_COUNTDOWN_HIGH
569 : SOUND_SPOKEN_FEEDBACK_TOGGLE_COUNTDOWN_LOW);
570 }
571
HandleAccessibilityGesture(ax::mojom::Gesture gesture,gfx::PointF location)572 void AccessibilityManager::HandleAccessibilityGesture(
573 ax::mojom::Gesture gesture,
574 gfx::PointF location) {
575 extensions::EventRouter* event_router =
576 extensions::EventRouter::Get(profile_);
577
578 std::unique_ptr<base::ListValue> event_args =
579 std::make_unique<base::ListValue>();
580 event_args->AppendString(ui::ToString(gesture));
581 event_args->AppendInteger(location.x());
582 event_args->AppendInteger(location.y());
583 std::unique_ptr<extensions::Event> event(new extensions::Event(
584 extensions::events::ACCESSIBILITY_PRIVATE_ON_ACCESSIBILITY_GESTURE,
585 extensions::api::accessibility_private::OnAccessibilityGesture::
586 kEventName,
587 std::move(event_args)));
588 event_router->DispatchEventWithLazyListener(
589 extension_misc::kChromeVoxExtensionId, std::move(event));
590 }
591
SetTouchAccessibilityAnchorPoint(const gfx::Point & anchor_point)592 void AccessibilityManager::SetTouchAccessibilityAnchorPoint(
593 const gfx::Point& anchor_point) {
594 for (auto* rwc : ash::RootWindowController::root_window_controllers())
595 rwc->SetTouchAccessibilityAnchorPoint(anchor_point);
596 }
597
EnableAutoclick(bool enabled)598 void AccessibilityManager::EnableAutoclick(bool enabled) {
599 if (!profile_)
600 return;
601
602 PrefService* pref_service = profile_->GetPrefs();
603 pref_service->SetBoolean(ash::prefs::kAccessibilityAutoclickEnabled, enabled);
604 pref_service->CommitPendingWrite();
605 }
606
IsAutoclickEnabled() const607 bool AccessibilityManager::IsAutoclickEnabled() const {
608 return profile_ && profile_->GetPrefs()->GetBoolean(
609 ash::prefs::kAccessibilityAutoclickEnabled);
610 }
611
OnAccessibilityCommonChanged(const std::string & pref_name)612 void AccessibilityManager::OnAccessibilityCommonChanged(
613 const std::string& pref_name) {
614 if (!profile_)
615 return;
616
617 const bool enabled = profile_->GetPrefs()->GetBoolean(pref_name);
618 if (enabled) {
619 accessibility_common_extension_loader_->SetProfile(
620 profile_, base::Closure() /* done_callback */);
621 }
622
623 size_t pref_count = accessibility_common_enabled_features_.count(pref_name);
624 if ((pref_count != 0 && enabled) || (pref_count == 0 && !enabled))
625 return;
626
627 if (enabled) {
628 accessibility_common_enabled_features_.insert(pref_name);
629 if (!accessibility_common_extension_loader_->loaded()) {
630 accessibility_common_extension_loader_->Load(
631 profile_, base::BindRepeating(
632 &AccessibilityManager::PostLoadAccessibilityCommon,
633 weak_ptr_factory_.GetWeakPtr()));
634 } else {
635 // It's already loaded. Just run the callback.
636 PostLoadAccessibilityCommon();
637 }
638 } else {
639 accessibility_common_enabled_features_.erase(pref_name);
640
641 if (accessibility_common_enabled_features_.empty()) {
642 accessibility_common_extension_loader_->Unload();
643 }
644 }
645 }
646
RequestAutoclickScrollableBoundsForPoint(gfx::Point & point_in_screen)647 void AccessibilityManager::RequestAutoclickScrollableBoundsForPoint(
648 gfx::Point& point_in_screen) {
649 extensions::EventRouter* event_router =
650 extensions::EventRouter::Get(profile_);
651 std::unique_ptr<base::ListValue> event_args = extensions::api::
652 accessibility_private::OnScrollableBoundsForPointRequested::Create(
653 point_in_screen.x(), point_in_screen.y());
654 std::unique_ptr<extensions::Event> event =
655 std::make_unique<extensions::Event>(
656 extensions::events::
657 ACCESSIBILITY_PRIVATE_FIND_SCROLLABLE_BOUNDS_FOR_POINT,
658 extensions::api::accessibility_private::
659 OnScrollableBoundsForPointRequested::kEventName,
660 std::move(event_args));
661 event_router->DispatchEventWithLazyListener(
662 extension_misc::kAccessibilityCommonExtensionId, std::move(event));
663 }
664
MagnifierBoundsChanged(const gfx::Rect & bounds_in_screen)665 void AccessibilityManager::MagnifierBoundsChanged(
666 const gfx::Rect& bounds_in_screen) {
667 if (!profile_)
668 return;
669
670 extensions::EventRouter* event_router =
671 extensions::EventRouter::Get(profile_);
672
673 auto magnifier_bounds =
674 std::make_unique<extensions::api::accessibility_private::ScreenRect>();
675 magnifier_bounds->left = bounds_in_screen.x();
676 magnifier_bounds->top = bounds_in_screen.y();
677 magnifier_bounds->width = bounds_in_screen.width();
678 magnifier_bounds->height = bounds_in_screen.height();
679
680 auto event_args =
681 extensions::api::accessibility_private::OnMagnifierBoundsChanged::Create(
682 *magnifier_bounds.get());
683
684 auto event = std::make_unique<extensions::Event>(
685 extensions::events::ACCESSIBILITY_PRIVATE_ON_MAGNIFIER_BOUNDS_CHANGED,
686 extensions::api::accessibility_private::OnMagnifierBoundsChanged::
687 kEventName,
688 std::move(event_args));
689
690 event_router->DispatchEventWithLazyListener(
691 extension_misc::kAccessibilityCommonExtensionId, std::move(event));
692 }
693
EnableVirtualKeyboard(bool enabled)694 void AccessibilityManager::EnableVirtualKeyboard(bool enabled) {
695 if (!profile_)
696 return;
697
698 PrefService* pref_service = profile_->GetPrefs();
699 pref_service->SetBoolean(ash::prefs::kAccessibilityVirtualKeyboardEnabled,
700 enabled);
701 pref_service->CommitPendingWrite();
702 }
703
IsVirtualKeyboardEnabled() const704 bool AccessibilityManager::IsVirtualKeyboardEnabled() const {
705 return profile_ && profile_->GetPrefs()->GetBoolean(
706 ash::prefs::kAccessibilityVirtualKeyboardEnabled);
707 }
708
OnVirtualKeyboardChanged()709 void AccessibilityManager::OnVirtualKeyboardChanged() {
710 AccessibilityStatusEventDetails details(ACCESSIBILITY_TOGGLE_VIRTUAL_KEYBOARD,
711 IsVirtualKeyboardEnabled());
712 NotifyAccessibilityStatusChanged(details);
713 }
714
EnableMonoAudio(bool enabled)715 void AccessibilityManager::EnableMonoAudio(bool enabled) {
716 if (!profile_)
717 return;
718
719 PrefService* pref_service = profile_->GetPrefs();
720 pref_service->SetBoolean(ash::prefs::kAccessibilityMonoAudioEnabled, enabled);
721 pref_service->CommitPendingWrite();
722 }
723
IsMonoAudioEnabled() const724 bool AccessibilityManager::IsMonoAudioEnabled() const {
725 return profile_ && profile_->GetPrefs()->GetBoolean(
726 ash::prefs::kAccessibilityMonoAudioEnabled);
727 }
728
OnMonoAudioChanged()729 void AccessibilityManager::OnMonoAudioChanged() {
730 AccessibilityStatusEventDetails details(ACCESSIBILITY_TOGGLE_MONO_AUDIO,
731 IsMonoAudioEnabled());
732 NotifyAccessibilityStatusChanged(details);
733 }
734
SetDarkenScreen(bool darken)735 void AccessibilityManager::SetDarkenScreen(bool darken) {
736 ash::AccessibilityController::Get()->SetDarkenScreen(darken);
737 }
738
SetCaretHighlightEnabled(bool enabled)739 void AccessibilityManager::SetCaretHighlightEnabled(bool enabled) {
740 if (!profile_)
741 return;
742
743 PrefService* pref_service = profile_->GetPrefs();
744 pref_service->SetBoolean(ash::prefs::kAccessibilityCaretHighlightEnabled,
745 enabled);
746 pref_service->CommitPendingWrite();
747 }
748
IsCaretHighlightEnabled() const749 bool AccessibilityManager::IsCaretHighlightEnabled() const {
750 return profile_ && profile_->GetPrefs()->GetBoolean(
751 ash::prefs::kAccessibilityCaretHighlightEnabled);
752 }
753
OnCaretHighlightChanged()754 void AccessibilityManager::OnCaretHighlightChanged() {
755 AccessibilityStatusEventDetails details(ACCESSIBILITY_TOGGLE_CARET_HIGHLIGHT,
756 IsCaretHighlightEnabled());
757 NotifyAccessibilityStatusChanged(details);
758 }
759
SetCursorHighlightEnabled(bool enabled)760 void AccessibilityManager::SetCursorHighlightEnabled(bool enabled) {
761 if (!profile_)
762 return;
763
764 PrefService* pref_service = profile_->GetPrefs();
765 pref_service->SetBoolean(ash::prefs::kAccessibilityCursorHighlightEnabled,
766 enabled);
767 pref_service->CommitPendingWrite();
768 }
769
IsCursorHighlightEnabled() const770 bool AccessibilityManager::IsCursorHighlightEnabled() const {
771 return profile_ && profile_->GetPrefs()->GetBoolean(
772 ash::prefs::kAccessibilityCursorHighlightEnabled);
773 }
774
OnCursorHighlightChanged()775 void AccessibilityManager::OnCursorHighlightChanged() {
776 AccessibilityStatusEventDetails details(ACCESSIBILITY_TOGGLE_CURSOR_HIGHLIGHT,
777 IsCursorHighlightEnabled());
778 NotifyAccessibilityStatusChanged(details);
779 }
780
IsDictationEnabled() const781 bool AccessibilityManager::IsDictationEnabled() const {
782 return profile_ && profile_->GetPrefs()->GetBoolean(
783 ash::prefs::kAccessibilityDictationEnabled);
784 }
785
SetFocusHighlightEnabled(bool enabled)786 void AccessibilityManager::SetFocusHighlightEnabled(bool enabled) {
787 if (!profile_)
788 return;
789
790 PrefService* pref_service = profile_->GetPrefs();
791 pref_service->SetBoolean(ash::prefs::kAccessibilityFocusHighlightEnabled,
792 enabled);
793 pref_service->CommitPendingWrite();
794 }
795
IsFocusHighlightEnabled() const796 bool AccessibilityManager::IsFocusHighlightEnabled() const {
797 return profile_ && profile_->GetPrefs()->GetBoolean(
798 ash::prefs::kAccessibilityFocusHighlightEnabled);
799 }
800
OnFocusHighlightChanged()801 void AccessibilityManager::OnFocusHighlightChanged() {
802 bool enabled = IsFocusHighlightEnabled();
803
804 // Focus highlighting can't be on when spoken feedback is on, because
805 // ChromeVox does its own focus highlighting.
806 if (IsSpokenFeedbackEnabled())
807 enabled = false;
808 AccessibilityStatusEventDetails details(ACCESSIBILITY_TOGGLE_FOCUS_HIGHLIGHT,
809 enabled);
810 NotifyAccessibilityStatusChanged(details);
811
812 // TODO(crbug.com/1096759): Load or unload the AccessibilityCommon extension
813 // which will be used to get the currently focused view. This will enable us
814 // to get views where focus is only represented in the accessibility tree,
815 // like those set by aria-activedescendant. It will also possibly eliminate
816 // the need for OnViewFocusedInArc.
817 }
818
SetSelectToSpeakEnabled(bool enabled)819 void AccessibilityManager::SetSelectToSpeakEnabled(bool enabled) {
820 if (!profile_)
821 return;
822
823 PrefService* pref_service = profile_->GetPrefs();
824 pref_service->SetBoolean(ash::prefs::kAccessibilitySelectToSpeakEnabled,
825 enabled);
826 pref_service->CommitPendingWrite();
827 }
828
IsSelectToSpeakEnabled() const829 bool AccessibilityManager::IsSelectToSpeakEnabled() const {
830 return select_to_speak_enabled_;
831 }
832
RequestSelectToSpeakStateChange()833 void AccessibilityManager::RequestSelectToSpeakStateChange() {
834 extensions::EventRouter* event_router =
835 extensions::EventRouter::Get(profile_);
836
837 // Send an event to the Select-to-Speak extension requesting a state change.
838 std::unique_ptr<base::ListValue> event_args =
839 std::make_unique<base::ListValue>();
840 std::unique_ptr<extensions::Event> event(new extensions::Event(
841 extensions::events::
842 ACCESSIBILITY_PRIVATE_ON_SELECT_TO_SPEAK_STATE_CHANGE_REQUESTED,
843 extensions::api::accessibility_private::
844 OnSelectToSpeakStateChangeRequested::kEventName,
845 std::move(event_args)));
846 event_router->DispatchEventWithLazyListener(
847 extension_misc::kSelectToSpeakExtensionId, std::move(event));
848 }
849
SetSelectToSpeakState(ash::SelectToSpeakState state)850 void AccessibilityManager::SetSelectToSpeakState(
851 ash::SelectToSpeakState state) {
852 ash::AccessibilityController::Get()->SetSelectToSpeakState(state);
853
854 if (select_to_speak_state_observer_for_test_)
855 select_to_speak_state_observer_for_test_.Run();
856 }
857
OnSelectToSpeakChanged()858 void AccessibilityManager::OnSelectToSpeakChanged() {
859 if (!profile_)
860 return;
861
862 const bool enabled = profile_->GetPrefs()->GetBoolean(
863 ash::prefs::kAccessibilitySelectToSpeakEnabled);
864 if (enabled)
865 select_to_speak_loader_->SetProfile(profile_, base::Closure());
866
867 if (select_to_speak_enabled_ == enabled)
868 return;
869
870 select_to_speak_enabled_ = enabled;
871
872 AccessibilityStatusEventDetails details(ACCESSIBILITY_TOGGLE_SELECT_TO_SPEAK,
873 enabled);
874 NotifyAccessibilityStatusChanged(details);
875
876 if (enabled) {
877 select_to_speak_loader_->Load(
878 profile_,
879 base::BindRepeating(&AccessibilityManager::PostLoadSelectToSpeak,
880 weak_ptr_factory_.GetWeakPtr()));
881 // Construct a delegate to connect SelectToSpeak and its EventHandler in
882 // ash.
883 select_to_speak_event_handler_delegate_ =
884 std::make_unique<chromeos::SelectToSpeakEventHandlerDelegate>();
885 } else {
886 select_to_speak_loader_->Unload();
887 select_to_speak_event_handler_delegate_.reset();
888 }
889 }
890
SetSwitchAccessEnabled(bool enabled)891 void AccessibilityManager::SetSwitchAccessEnabled(bool enabled) {
892 if (!profile_)
893 return;
894
895 PrefService* pref_service = profile_->GetPrefs();
896 pref_service->SetBoolean(ash::prefs::kAccessibilitySwitchAccessEnabled,
897 enabled);
898 pref_service->CommitPendingWrite();
899 }
900
IsSwitchAccessEnabled() const901 bool AccessibilityManager::IsSwitchAccessEnabled() const {
902 return switch_access_enabled_;
903 }
904
OnSwitchAccessChanged()905 void AccessibilityManager::OnSwitchAccessChanged() {
906 if (!profile_)
907 return;
908
909 const bool enabled = profile_->GetPrefs()->GetBoolean(
910 ash::prefs::kAccessibilitySwitchAccessEnabled);
911
912 if (enabled) {
913 // Only update |was_vk_enabled_before_switch_access_| if the profile
914 // changed.
915 if (profile_ != switch_access_loader_->profile()) {
916 was_vk_enabled_before_switch_access_ =
917 ChromeKeyboardControllerClient::Get()->IsEnableFlagSet(
918 keyboard::KeyboardEnableFlag::kExtensionEnabled);
919 }
920
921 switch_access_loader_->SetProfile(profile_, base::Closure());
922
923 // Make sure we always update the VK state, on every profile transition.
924 ChromeKeyboardControllerClient::Get()->SetEnableFlag(
925 keyboard::KeyboardEnableFlag::kExtensionEnabled);
926 }
927
928 if (switch_access_enabled_ == enabled)
929 return;
930 switch_access_enabled_ = enabled;
931
932 AccessibilityStatusEventDetails details(ACCESSIBILITY_TOGGLE_SWITCH_ACCESS,
933 enabled);
934 NotifyAccessibilityStatusChanged(details);
935
936 if (enabled) {
937 switch_access_loader_->Load(
938 profile_,
939 base::BindRepeating(&AccessibilityManager::PostLoadSwitchAccess,
940 weak_ptr_factory_.GetWeakPtr()));
941 }
942 }
943
OnSwitchAccessDisabled()944 void AccessibilityManager::OnSwitchAccessDisabled() {
945 switch_access_loader_->Unload();
946 }
947
IsBrailleDisplayConnected() const948 bool AccessibilityManager::IsBrailleDisplayConnected() const {
949 return braille_display_connected_;
950 }
951
CheckBrailleState()952 void AccessibilityManager::CheckBrailleState() {
953 BrailleController* braille_controller = GetBrailleController();
954 if (!scoped_braille_observer_.IsObserving(braille_controller))
955 scoped_braille_observer_.Add(braille_controller);
956 content::GetIOThreadTaskRunner({})->PostTaskAndReplyWithResult(
957 FROM_HERE,
958 base::BindOnce(&BrailleController::GetDisplayState,
959 base::Unretained(braille_controller)),
960 base::BindOnce(&AccessibilityManager::ReceiveBrailleDisplayState,
961 weak_ptr_factory_.GetWeakPtr()));
962 }
963
ReceiveBrailleDisplayState(std::unique_ptr<extensions::api::braille_display_private::DisplayState> state)964 void AccessibilityManager::ReceiveBrailleDisplayState(
965 std::unique_ptr<extensions::api::braille_display_private::DisplayState>
966 state) {
967 OnBrailleDisplayStateChanged(*state);
968 }
969
UpdateBrailleImeState()970 void AccessibilityManager::UpdateBrailleImeState() {
971 if (!profile_)
972 return;
973 PrefService* pref_service = profile_->GetPrefs();
974 std::string preload_engines_str =
975 pref_service->GetString(prefs::kLanguagePreloadEngines);
976 std::vector<base::StringPiece> preload_engines = base::SplitStringPiece(
977 preload_engines_str, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
978 std::vector<base::StringPiece>::iterator it =
979 std::find(preload_engines.begin(), preload_engines.end(),
980 extension_ime_util::kBrailleImeEngineId);
981 bool is_enabled = (it != preload_engines.end());
982 bool should_be_enabled =
983 (IsSpokenFeedbackEnabled() && braille_display_connected_);
984 if (is_enabled == should_be_enabled)
985 return;
986 if (should_be_enabled)
987 preload_engines.push_back(extension_ime_util::kBrailleImeEngineId);
988 else
989 preload_engines.erase(it);
990 pref_service->SetString(prefs::kLanguagePreloadEngines,
991 base::JoinString(preload_engines, ","));
992 braille_ime_current_ = false;
993 }
994
995 // Overridden from InputMethodManager::Observer.
InputMethodChanged(input_method::InputMethodManager * manager,Profile *,bool show_message)996 void AccessibilityManager::InputMethodChanged(
997 input_method::InputMethodManager* manager,
998 Profile* /* profile */,
999 bool show_message) {
1000 ash::Shell::Get()->sticky_keys_controller()->SetModifiersEnabled(
1001 manager->IsISOLevel5ShiftUsedByCurrentInputMethod(),
1002 manager->IsAltGrUsedByCurrentInputMethod());
1003 const chromeos::input_method::InputMethodDescriptor descriptor =
1004 manager->GetActiveIMEState()->GetCurrentInputMethod();
1005 braille_ime_current_ =
1006 (descriptor.id() == extension_ime_util::kBrailleImeEngineId);
1007 }
1008
OnActiveOutputNodeChanged()1009 void AccessibilityManager::OnActiveOutputNodeChanged() {
1010 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
1011 chromeos::switches::kFirstExecAfterBoot))
1012 return;
1013
1014 AudioDevice device;
1015 CrasAudioHandler::Get()->GetPrimaryActiveOutputDevice(&device);
1016 if (device.type == AudioDeviceType::AUDIO_TYPE_OTHER)
1017 return;
1018
1019 CrasAudioHandler::Get()->RemoveAudioObserver(this);
1020 if (GetStartupSoundEnabled()) {
1021 PlayEarcon(SOUND_STARTUP, PlaySoundOption::ALWAYS);
1022 return;
1023 }
1024
1025 const auto& account_ids = user_manager::known_user::GetKnownAccountIds();
1026 for (size_t i = 0; i < account_ids.size(); ++i) {
1027 bool val;
1028 if (user_manager::known_user::GetBooleanPref(
1029 account_ids[i], kUserSpokenFeedbackEnabled, &val) &&
1030 val) {
1031 PlayEarcon(SOUND_STARTUP, PlaySoundOption::ALWAYS);
1032 break;
1033 }
1034 }
1035 }
1036
OnProfileWillBeDestroyed(Profile * profile)1037 void AccessibilityManager::OnProfileWillBeDestroyed(Profile* profile) {
1038 DCHECK_EQ(profile_, profile);
1039 SetProfile(nullptr);
1040 }
1041
SetProfile(Profile * profile)1042 void AccessibilityManager::SetProfile(Profile* profile) {
1043 if (profile_ == profile)
1044 return;
1045
1046 if (profile_)
1047 profile_observer_.Remove(profile_);
1048 DCHECK(!profile_observer_.IsObservingSources());
1049
1050 pref_change_registrar_.reset();
1051 local_state_pref_change_registrar_.reset();
1052
1053 // Clear all dictation state on profile change.
1054 dictation_.reset();
1055
1056 // All features supported by accessibility common.
1057 static const char* kAccessibilityCommonFeatures[] = {
1058 ash::prefs::kAccessibilityAutoclickEnabled,
1059 ash::prefs::kAccessibilityScreenMagnifierEnabled,
1060 ash::prefs::kDockedMagnifierEnabled};
1061
1062 if (profile) {
1063 // TODO(yoshiki): Move following code to PrefHandler.
1064 pref_change_registrar_.reset(new PrefChangeRegistrar);
1065 pref_change_registrar_->Init(profile->GetPrefs());
1066 pref_change_registrar_->Add(
1067 ash::prefs::kShouldAlwaysShowAccessibilityMenu,
1068 base::BindRepeating(&AccessibilityManager::UpdateAlwaysShowMenuFromPref,
1069 base::Unretained(this)));
1070 pref_change_registrar_->Add(
1071 ash::prefs::kAccessibilityLargeCursorEnabled,
1072 base::BindRepeating(&AccessibilityManager::OnLargeCursorChanged,
1073 base::Unretained(this)));
1074 pref_change_registrar_->Add(
1075 ash::prefs::kAccessibilityLargeCursorDipSize,
1076 base::BindRepeating(&AccessibilityManager::OnLargeCursorChanged,
1077 base::Unretained(this)));
1078 pref_change_registrar_->Add(
1079 ash::prefs::kAccessibilityStickyKeysEnabled,
1080 base::BindRepeating(&AccessibilityManager::OnStickyKeysChanged,
1081 base::Unretained(this)));
1082 pref_change_registrar_->Add(
1083 ash::prefs::kAccessibilitySpokenFeedbackEnabled,
1084 base::BindRepeating(&AccessibilityManager::OnSpokenFeedbackChanged,
1085 base::Unretained(this)));
1086 pref_change_registrar_->Add(
1087 ash::prefs::kAccessibilityHighContrastEnabled,
1088 base::BindRepeating(&AccessibilityManager::OnHighContrastChanged,
1089 base::Unretained(this)));
1090 pref_change_registrar_->Add(
1091 ash::prefs::kAccessibilityVirtualKeyboardEnabled,
1092 base::BindRepeating(&AccessibilityManager::OnVirtualKeyboardChanged,
1093 base::Unretained(this)));
1094 pref_change_registrar_->Add(
1095 ash::prefs::kAccessibilityMonoAudioEnabled,
1096 base::BindRepeating(&AccessibilityManager::OnMonoAudioChanged,
1097 base::Unretained(this)));
1098 pref_change_registrar_->Add(
1099 ash::prefs::kAccessibilityCaretHighlightEnabled,
1100 base::BindRepeating(&AccessibilityManager::OnCaretHighlightChanged,
1101 base::Unretained(this)));
1102 pref_change_registrar_->Add(
1103 ash::prefs::kAccessibilityCursorHighlightEnabled,
1104 base::BindRepeating(&AccessibilityManager::OnCursorHighlightChanged,
1105 base::Unretained(this)));
1106 pref_change_registrar_->Add(
1107 ash::prefs::kAccessibilityFocusHighlightEnabled,
1108 base::BindRepeating(&AccessibilityManager::OnFocusHighlightChanged,
1109 base::Unretained(this)));
1110 pref_change_registrar_->Add(
1111 ash::prefs::kAccessibilitySelectToSpeakEnabled,
1112 base::BindRepeating(&AccessibilityManager::OnSelectToSpeakChanged,
1113 base::Unretained(this)));
1114 pref_change_registrar_->Add(
1115 ash::prefs::kAccessibilitySwitchAccessEnabled,
1116 base::BindRepeating(&AccessibilityManager::OnSwitchAccessChanged,
1117 base::Unretained(this)));
1118
1119 for (const std::string& feature : kAccessibilityCommonFeatures) {
1120 pref_change_registrar_->Add(
1121 feature, base::BindRepeating(
1122 &AccessibilityManager::OnAccessibilityCommonChanged,
1123 base::Unretained(this)));
1124 }
1125
1126 local_state_pref_change_registrar_.reset(new PrefChangeRegistrar);
1127 local_state_pref_change_registrar_->Init(g_browser_process->local_state());
1128 local_state_pref_change_registrar_->Add(
1129 language::prefs::kApplicationLocale,
1130 base::BindRepeating(&AccessibilityManager::OnLocaleChanged,
1131 base::Unretained(this)));
1132
1133 // Compute these histograms on the main (UI) thread because they
1134 // need to access PrefService.
1135 content::BrowserAccessibilityState::GetInstance()
1136 ->AddUIThreadHistogramCallback(base::BindOnce(
1137 &AccessibilityManager::UpdateChromeOSAccessibilityHistograms,
1138 base::Unretained(this)));
1139
1140 extensions::ExtensionRegistry* registry =
1141 extensions::ExtensionRegistry::Get(profile);
1142 if (!extension_registry_observer_.IsObserving(registry))
1143 extension_registry_observer_.Add(registry);
1144
1145 profile_observer_.Add(profile);
1146 }
1147
1148 bool had_profile = (profile_ != NULL);
1149 profile_ = profile;
1150
1151 if (!had_profile && profile)
1152 CheckBrailleState();
1153 else
1154 UpdateBrailleImeState();
1155 UpdateAlwaysShowMenuFromPref();
1156
1157 // TODO(warx): reconcile to ash once the prefs registration above is moved to
1158 // ash.
1159 OnSpokenFeedbackChanged();
1160 OnSwitchAccessChanged();
1161 OnSelectToSpeakChanged();
1162
1163 for (const std::string& feature : kAccessibilityCommonFeatures)
1164 OnAccessibilityCommonChanged(feature);
1165 }
1166
SetProfileByUser(const user_manager::User * user)1167 void AccessibilityManager::SetProfileByUser(const user_manager::User* user) {
1168 Profile* profile = ProfileHelper::Get()->GetProfileByUser(user);
1169 DCHECK(profile);
1170 SetProfile(profile);
1171 }
1172
ActiveUserChanged(user_manager::User * active_user)1173 void AccessibilityManager::ActiveUserChanged(user_manager::User* active_user) {
1174 if (!active_user)
1175 return;
1176
1177 active_user->AddProfileCreatedObserver(
1178 base::BindOnce(&AccessibilityManager::SetProfileByUser,
1179 weak_ptr_factory_.GetWeakPtr(), active_user));
1180 }
1181
PlayShutdownSound()1182 base::TimeDelta AccessibilityManager::PlayShutdownSound() {
1183 if (!PlayEarcon(SOUND_SHUTDOWN,
1184 PlaySoundOption::ONLY_IF_SPOKEN_FEEDBACK_ENABLED)) {
1185 return base::TimeDelta();
1186 }
1187 return audio::SoundsManager::Get()->GetDuration(SOUND_SHUTDOWN);
1188 }
1189
1190 std::unique_ptr<AccessibilityStatusSubscription>
RegisterCallback(const AccessibilityStatusCallback & cb)1191 AccessibilityManager::RegisterCallback(const AccessibilityStatusCallback& cb) {
1192 return callback_list_.Add(cb);
1193 }
1194
NotifyAccessibilityStatusChanged(const AccessibilityStatusEventDetails & details)1195 void AccessibilityManager::NotifyAccessibilityStatusChanged(
1196 const AccessibilityStatusEventDetails& details) {
1197 callback_list_.Notify(details);
1198
1199 if (details.notification_type == ACCESSIBILITY_TOGGLE_DICTATION) {
1200 ash::AccessibilityController::Get()->SetDictationActive(details.enabled);
1201 ash::AccessibilityController::Get()->NotifyAccessibilityStatusChanged();
1202 return;
1203 }
1204
1205 // Update system tray menu visibility. Prefs tracked inside ash handle their
1206 // own updates to avoid race conditions (pref updates are asynchronous between
1207 // chrome and ash).
1208 if (details.notification_type == ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFIER ||
1209 details.notification_type == ACCESSIBILITY_TOGGLE_DICTATION) {
1210 ash::AccessibilityController::Get()->NotifyAccessibilityStatusChanged();
1211 }
1212 }
1213
UpdateChromeOSAccessibilityHistograms()1214 void AccessibilityManager::UpdateChromeOSAccessibilityHistograms() {
1215 base::UmaHistogramBoolean("Accessibility.CrosSpokenFeedback",
1216 IsSpokenFeedbackEnabled());
1217 base::UmaHistogramBoolean("Accessibility.CrosHighContrast",
1218 IsHighContrastEnabled());
1219 base::UmaHistogramBoolean("Accessibility.CrosVirtualKeyboard",
1220 IsVirtualKeyboardEnabled());
1221 base::UmaHistogramBoolean("Accessibility.CrosStickyKeys",
1222 IsStickyKeysEnabled());
1223 if (MagnificationManager::Get()) {
1224 base::UmaHistogramBoolean(
1225 "Accessibility.CrosScreenMagnifier",
1226 MagnificationManager::Get()->IsMagnifierEnabled());
1227 base::UmaHistogramBoolean(
1228 "Accessibility.CrosDockedMagnifier",
1229 MagnificationManager::Get()->IsDockedMagnifierEnabled());
1230 }
1231 if (profile_) {
1232 const PrefService* const prefs = profile_->GetPrefs();
1233
1234 bool large_cursor_enabled =
1235 prefs->GetBoolean(ash::prefs::kAccessibilityLargeCursorEnabled);
1236 base::UmaHistogramBoolean("Accessibility.CrosLargeCursor",
1237 large_cursor_enabled);
1238 if (large_cursor_enabled) {
1239 base::UmaHistogramCounts100(
1240 "Accessibility.CrosLargeCursorSize",
1241 prefs->GetInteger(ash::prefs::kAccessibilityLargeCursorDipSize));
1242 }
1243
1244 base::UmaHistogramBoolean(
1245 "Accessibility.CrosAlwaysShowA11yMenu",
1246 prefs->GetBoolean(ash::prefs::kShouldAlwaysShowAccessibilityMenu));
1247
1248 bool autoclick_enabled =
1249 prefs->GetBoolean(ash::prefs::kAccessibilityAutoclickEnabled);
1250 base::UmaHistogramBoolean("Accessibility.CrosAutoclick", autoclick_enabled);
1251
1252 base::UmaHistogramBoolean(
1253 "Accessibility.CrosCursorColor",
1254 prefs->GetBoolean(ash::prefs::kAccessibilityCursorColorEnabled));
1255 }
1256 base::UmaHistogramBoolean("Accessibility.CrosCaretHighlight",
1257 IsCaretHighlightEnabled());
1258 base::UmaHistogramBoolean("Accessibility.CrosCursorHighlight",
1259 IsCursorHighlightEnabled());
1260 base::UmaHistogramBoolean("Accessibility.CrosDictation",
1261 IsDictationEnabled());
1262 base::UmaHistogramBoolean("Accessibility.CrosFocusHighlight",
1263 IsFocusHighlightEnabled());
1264 base::UmaHistogramBoolean("Accessibility.CrosSelectToSpeak",
1265 IsSelectToSpeakEnabled());
1266 base::UmaHistogramBoolean("Accessibility.CrosSwitchAccess",
1267 IsSwitchAccessEnabled());
1268 }
1269
PlayVolumeAdjustSound()1270 void AccessibilityManager::PlayVolumeAdjustSound() {
1271 if (VolumeAdjustSoundEnabled()) {
1272 PlayEarcon(chromeos::SOUND_VOLUME_ADJUST,
1273 chromeos::PlaySoundOption::ONLY_IF_SPOKEN_FEEDBACK_ENABLED);
1274 }
1275 }
1276
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)1277 void AccessibilityManager::Observe(
1278 int type,
1279 const content::NotificationSource& source,
1280 const content::NotificationDetails& details) {
1281 switch (type) {
1282 case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE: {
1283 // Update |profile_| when entering the login screen.
1284 Profile* profile = ProfileManager::GetActiveUserProfile();
1285 if (ProfileHelper::IsSigninProfile(profile))
1286 SetProfile(profile);
1287 break;
1288 }
1289 case chrome::NOTIFICATION_APP_TERMINATING: {
1290 app_terminating_ = true;
1291 break;
1292 }
1293 case content::NOTIFICATION_FOCUS_CHANGED_IN_PAGE: {
1294 // Avoid unnecessary mojo IPC to ash when focus highlight feature is not
1295 // enabled.
1296 if (!IsFocusHighlightEnabled())
1297 return;
1298 content::FocusedNodeDetails* node_details =
1299 content::Details<content::FocusedNodeDetails>(details).ptr();
1300 ash::AccessibilityController::Get()->SetFocusHighlightRect(
1301 node_details->node_bounds_in_screen);
1302 break;
1303 }
1304 }
1305 }
1306
OnBrailleDisplayStateChanged(const DisplayState & display_state)1307 void AccessibilityManager::OnBrailleDisplayStateChanged(
1308 const DisplayState& display_state) {
1309 braille_display_connected_ = display_state.available;
1310 ash::AccessibilityController::Get()->BrailleDisplayStateChanged(
1311 braille_display_connected_);
1312 UpdateBrailleImeState();
1313 }
1314
OnBrailleKeyEvent(const KeyEvent & event)1315 void AccessibilityManager::OnBrailleKeyEvent(const KeyEvent& event) {
1316 // Ensure the braille IME is active on braille keyboard (dots) input.
1317 if ((event.command ==
1318 extensions::api::braille_display_private::KEY_COMMAND_DOTS) &&
1319 !braille_ime_current_) {
1320 input_method::InputMethodManager::Get()
1321 ->GetActiveIMEState()
1322 ->ChangeInputMethod(extension_ime_util::kBrailleImeEngineId,
1323 false /* show_message */);
1324 }
1325 }
1326
OnExtensionUnloaded(content::BrowserContext * browser_context,const extensions::Extension * extension,extensions::UnloadedExtensionReason reason)1327 void AccessibilityManager::OnExtensionUnloaded(
1328 content::BrowserContext* browser_context,
1329 const extensions::Extension* extension,
1330 extensions::UnloadedExtensionReason reason) {
1331 if (extension->id() == keyboard_listener_extension_id_)
1332 keyboard_listener_extension_id_ = std::string();
1333
1334 if (extension->id() == extension_misc::kSwitchAccessExtensionId) {
1335 extensions::VirtualKeyboardAPI* api =
1336 extensions::BrowserContextKeyedAPIFactory<
1337 extensions::VirtualKeyboardAPI>::Get(browser_context);
1338 DCHECK(api);
1339 api->delegate()->SetRequestedKeyboardState(
1340 extensions::api::virtual_keyboard_private::KEYBOARD_STATE_AUTO);
1341 }
1342 }
1343
OnShutdown(extensions::ExtensionRegistry * registry)1344 void AccessibilityManager::OnShutdown(extensions::ExtensionRegistry* registry) {
1345 extension_registry_observer_.Remove(registry);
1346 }
1347
PostLoadChromeVox()1348 void AccessibilityManager::PostLoadChromeVox() {
1349 // In browser_tests loading the ChromeVox extension can race with shutdown.
1350 // http://crbug.com/801700
1351 if (app_terminating_)
1352 return;
1353
1354 // Do any setup work needed immediately after ChromeVox actually loads.
1355 const std::string& address = GetBluetoothBrailleDisplayAddress();
1356 if (!address.empty()) {
1357 // Maybe start brltty, when we have a bluetooth device stored for
1358 // connection.
1359 RestartBrltty(address);
1360 } else {
1361 // Otherwise, start brltty without an address. This covers cases when
1362 // ChromeVox is toggled off then back on all while a usb braille display is
1363 // connected.
1364 chromeos::UpstartClient::Get()->StartJob(kBrlttyUpstartJobName, {},
1365 base::DoNothing());
1366 }
1367
1368 PlayEarcon(SOUND_SPOKEN_FEEDBACK_ENABLED, PlaySoundOption::ALWAYS);
1369
1370 extensions::EventRouter* event_router =
1371 extensions::EventRouter::Get(profile_);
1372
1373 const std::string& extension_id = extension_misc::kChromeVoxExtensionId;
1374
1375 std::unique_ptr<base::ListValue> event_args =
1376 std::make_unique<base::ListValue>();
1377 std::unique_ptr<extensions::Event> event(new extensions::Event(
1378 extensions::events::ACCESSIBILITY_PRIVATE_ON_INTRODUCE_CHROME_VOX,
1379 extensions::api::accessibility_private::OnIntroduceChromeVox::kEventName,
1380 std::move(event_args)));
1381 event_router->DispatchEventWithLazyListener(extension_id, std::move(event));
1382
1383 if (!chromevox_panel_) {
1384 chromevox_panel_ = new ChromeVoxPanel(profile_);
1385 chromevox_panel_widget_observer_.reset(new AccessibilityPanelWidgetObserver(
1386 chromevox_panel_->GetWidget(),
1387 base::BindOnce(&AccessibilityManager::OnChromeVoxPanelDestroying,
1388 base::Unretained(this))));
1389 }
1390
1391 audio_focus_manager_->SetEnforcementMode(
1392 media_session::mojom::EnforcementMode::kNone);
1393
1394 InitializeFocusRings(extension_id);
1395 }
1396
PostUnloadChromeVox()1397 void AccessibilityManager::PostUnloadChromeVox() {
1398 // Do any teardown work needed immediately after ChromeVox actually unloads.
1399 // Stop brltty.
1400 chromeos::UpstartClient::Get()->StopJob(kBrlttyUpstartJobName, {},
1401 base::DoNothing());
1402
1403 PlayEarcon(SOUND_SPOKEN_FEEDBACK_DISABLED, PlaySoundOption::ALWAYS);
1404
1405 RemoveFocusRings(extension_misc::kChromeVoxExtensionId);
1406
1407 if (chromevox_panel_) {
1408 chromevox_panel_->Close();
1409 chromevox_panel_ = nullptr;
1410 }
1411
1412 // In case the user darkened the screen, undarken it now.
1413 SetDarkenScreen(false);
1414
1415 // Stop speech.
1416 content::TtsController::GetInstance()->Stop();
1417
1418 audio_focus_manager_->SetEnforcementMode(
1419 media_session::mojom::EnforcementMode::kDefault);
1420 }
1421
PostSwitchChromeVoxProfile()1422 void AccessibilityManager::PostSwitchChromeVoxProfile() {
1423 if (chromevox_panel_) {
1424 chromevox_panel_->CloseNow();
1425 chromevox_panel_ = nullptr;
1426 }
1427 chromevox_panel_ = new ChromeVoxPanel(profile_);
1428 chromevox_panel_widget_observer_.reset(new AccessibilityPanelWidgetObserver(
1429 chromevox_panel_->GetWidget(),
1430 base::BindOnce(&AccessibilityManager::OnChromeVoxPanelDestroying,
1431 base::Unretained(this))));
1432 }
1433
OnChromeVoxPanelDestroying()1434 void AccessibilityManager::OnChromeVoxPanelDestroying() {
1435 chromevox_panel_widget_observer_.reset(nullptr);
1436 chromevox_panel_ = nullptr;
1437 }
1438
PostLoadSelectToSpeak()1439 void AccessibilityManager::PostLoadSelectToSpeak() {
1440 InitializeFocusRings(extension_misc::kSelectToSpeakExtensionId);
1441 }
1442
PostUnloadSelectToSpeak()1443 void AccessibilityManager::PostUnloadSelectToSpeak() {
1444 // Do any teardown work needed immediately after Select-to-Speak actually
1445 // unloads.
1446
1447 // Clear the accessibility focus ring and highlight.
1448 RemoveFocusRings(extension_misc::kSelectToSpeakExtensionId);
1449 HideHighlights();
1450
1451 // Stop speech.
1452 content::TtsController::GetInstance()->Stop();
1453 }
1454
PostLoadSwitchAccess()1455 void AccessibilityManager::PostLoadSwitchAccess() {
1456 InitializeFocusRings(extension_misc::kSwitchAccessExtensionId);
1457 }
1458
PostUnloadSwitchAccess()1459 void AccessibilityManager::PostUnloadSwitchAccess() {
1460 // Do any teardown work needed immediately after Switch Access actually
1461 // unloads.
1462
1463 // Clear the accessibility focus ring.
1464 RemoveFocusRings(extension_misc::kSwitchAccessExtensionId);
1465
1466 if (!was_vk_enabled_before_switch_access_) {
1467 ChromeKeyboardControllerClient::Get()->ClearEnableFlag(
1468 keyboard::KeyboardEnableFlag::kExtensionEnabled);
1469 } else {
1470 was_vk_enabled_before_switch_access_ = false;
1471 }
1472 }
1473
PostLoadAccessibilityCommon()1474 void AccessibilityManager::PostLoadAccessibilityCommon() {
1475 // Do any setup work needed immediately after the Accessibility Common
1476 // extension actually loads. This may be used by all features which make
1477 // use of the Accessibility Common extension.
1478 InitializeFocusRings(extension_misc::kAccessibilityCommonExtensionId);
1479 }
1480
PostUnloadAccessibilityCommon()1481 void AccessibilityManager::PostUnloadAccessibilityCommon() {
1482 // Do any teardown work needed immediately after the Accessibility Common
1483 // extension actually unloads. This may be used by all features which make
1484 // use of the Accessibility Common extension.
1485 RemoveFocusRings(extension_misc::kAccessibilityCommonExtensionId);
1486 }
1487
SetKeyboardListenerExtensionId(const std::string & id,content::BrowserContext * context)1488 void AccessibilityManager::SetKeyboardListenerExtensionId(
1489 const std::string& id,
1490 content::BrowserContext* context) {
1491 keyboard_listener_extension_id_ = id;
1492
1493 extensions::ExtensionRegistry* registry =
1494 extensions::ExtensionRegistry::Get(context);
1495 if (!extension_registry_observer_.IsObserving(registry) && !id.empty())
1496 extension_registry_observer_.Add(registry);
1497 }
1498
ToggleDictation()1499 bool AccessibilityManager::ToggleDictation() {
1500 if (!profile_)
1501 return false;
1502
1503 if (!dictation_.get())
1504 dictation_ = std::make_unique<DictationChromeos>(profile_);
1505
1506 return dictation_->OnToggleDictation();
1507 }
1508
GetFocusRingId(const std::string & extension_id,const std::string & focus_ring_name)1509 const std::string AccessibilityManager::GetFocusRingId(
1510 const std::string& extension_id,
1511 const std::string& focus_ring_name) {
1512 // Add the focus ring name to the list of focus rings for the extension.
1513 focus_ring_names_for_extension_id_.find(extension_id)
1514 ->second.insert(focus_ring_name);
1515 return extension_id + '-' + focus_ring_name;
1516 }
1517
InitializeFocusRings(const std::string & extension_id)1518 void AccessibilityManager::InitializeFocusRings(
1519 const std::string& extension_id) {
1520 if (focus_ring_names_for_extension_id_.count(extension_id) == 0) {
1521 focus_ring_names_for_extension_id_.emplace(extension_id,
1522 std::set<std::string>());
1523 }
1524 }
1525
RemoveFocusRings(const std::string & extension_id)1526 void AccessibilityManager::RemoveFocusRings(const std::string& extension_id) {
1527 if (focus_ring_names_for_extension_id_.count(extension_id) != 0) {
1528 const std::set<std::string>& focus_ring_names =
1529 focus_ring_names_for_extension_id_.find(extension_id)->second;
1530
1531 for (const std::string& focus_ring_name : focus_ring_names)
1532 HideFocusRing(GetFocusRingId(extension_id, focus_ring_name));
1533 }
1534 focus_ring_names_for_extension_id_.erase(extension_id);
1535 }
1536
SetFocusRing(std::string focus_ring_id,std::unique_ptr<ash::AccessibilityFocusRingInfo> focus_ring)1537 void AccessibilityManager::SetFocusRing(
1538 std::string focus_ring_id,
1539 std::unique_ptr<ash::AccessibilityFocusRingInfo> focus_ring) {
1540 ash::AccessibilityFocusRingController::Get()->SetFocusRing(
1541 focus_ring_id, std::move(focus_ring));
1542
1543 if (focus_ring_observer_for_test_)
1544 focus_ring_observer_for_test_.Run();
1545 }
1546
HideFocusRing(std::string focus_ring_id)1547 void AccessibilityManager::HideFocusRing(std::string focus_ring_id) {
1548 ash::AccessibilityFocusRingController::Get()->HideFocusRing(focus_ring_id);
1549 if (focus_ring_observer_for_test_)
1550 focus_ring_observer_for_test_.Run();
1551 }
1552
SetHighlights(const std::vector<gfx::Rect> & rects_in_screen,SkColor color)1553 void AccessibilityManager::SetHighlights(
1554 const std::vector<gfx::Rect>& rects_in_screen,
1555 SkColor color) {
1556 ash::AccessibilityFocusRingController::Get()->SetHighlights(rects_in_screen,
1557 color);
1558 }
1559
HideHighlights()1560 void AccessibilityManager::HideHighlights() {
1561 ash::AccessibilityFocusRingController::Get()->HideHighlights();
1562 }
1563
SetCaretBounds(const gfx::Rect & bounds_in_screen)1564 void AccessibilityManager::SetCaretBounds(const gfx::Rect& bounds_in_screen) {
1565 // For efficiency only send mojo IPCs to ash if the highlight is enabled.
1566 if (!IsCaretHighlightEnabled())
1567 return;
1568
1569 ash::AccessibilityController::Get()->SetCaretBounds(bounds_in_screen);
1570
1571 if (caret_bounds_observer_for_test_)
1572 caret_bounds_observer_for_test_.Run(bounds_in_screen);
1573 }
1574
GetStartupSoundEnabled() const1575 bool AccessibilityManager::GetStartupSoundEnabled() const {
1576 user_manager::UserManager* user_manager = user_manager::UserManager::Get();
1577 const user_manager::UserList& user_list = user_manager->GetUsers();
1578 if (user_list.empty())
1579 return false;
1580
1581 // |user_list| is sorted by last log in date. Take the most recent user to
1582 // log in.
1583 bool val;
1584 return user_manager::known_user::GetBooleanPref(
1585 user_list[0]->GetAccountId(), kUserStartupSoundEnabled, &val) &&
1586 val;
1587 }
1588
SetStartupSoundEnabled(bool value) const1589 void AccessibilityManager::SetStartupSoundEnabled(bool value) const {
1590 if (!profile_)
1591 return;
1592
1593 user_manager::known_user::SetBooleanPref(
1594 multi_user_util::GetAccountIdFromProfile(profile_),
1595 kUserStartupSoundEnabled, value);
1596 }
1597
GetBluetoothBrailleDisplayAddress() const1598 const std::string AccessibilityManager::GetBluetoothBrailleDisplayAddress()
1599 const {
1600 user_manager::UserManager* user_manager = user_manager::UserManager::Get();
1601 const user_manager::UserList& user_list = user_manager->GetUsers();
1602 if (user_list.empty())
1603 return std::string();
1604
1605 // |user_list| is sorted by last log in date. Take the most recent user to
1606 // log in.
1607 std::string val;
1608 return user_manager::known_user::GetStringPref(
1609 user_list[0]->GetAccountId(), kUserBluetoothBrailleDisplayAddress,
1610 &val)
1611 ? val
1612 : std::string();
1613 }
1614
UpdateBluetoothBrailleDisplayAddress(const std::string & address)1615 void AccessibilityManager::UpdateBluetoothBrailleDisplayAddress(
1616 const std::string& address) {
1617 CHECK(spoken_feedback_enabled_);
1618 if (!profile_)
1619 return;
1620
1621 user_manager::known_user::SetStringPref(
1622 multi_user_util::GetAccountIdFromProfile(profile_),
1623 kUserBluetoothBrailleDisplayAddress, address);
1624 RestartBrltty(address);
1625 }
1626
SetProfileForTest(Profile * profile)1627 void AccessibilityManager::SetProfileForTest(Profile* profile) {
1628 SetProfile(profile);
1629 }
1630
1631 // static
SetBrailleControllerForTest(BrailleController * controller)1632 void AccessibilityManager::SetBrailleControllerForTest(
1633 BrailleController* controller) {
1634 g_braille_controller_for_test = controller;
1635 }
1636
SetFocusRingObserverForTest(base::RepeatingCallback<void ()> observer)1637 void AccessibilityManager::SetFocusRingObserverForTest(
1638 base::RepeatingCallback<void()> observer) {
1639 focus_ring_observer_for_test_ = observer;
1640 }
1641
SetSelectToSpeakStateObserverForTest(base::RepeatingCallback<void ()> observer)1642 void AccessibilityManager::SetSelectToSpeakStateObserverForTest(
1643 base::RepeatingCallback<void()> observer) {
1644 select_to_speak_state_observer_for_test_ = observer;
1645 }
1646
SetCaretBoundsObserverForTest(base::RepeatingCallback<void (const gfx::Rect &)> observer)1647 void AccessibilityManager::SetCaretBoundsObserverForTest(
1648 base::RepeatingCallback<void(const gfx::Rect&)> observer) {
1649 caret_bounds_observer_for_test_ = observer;
1650 }
1651
SetSwitchAccessKeysForTest(const std::vector<int> & keys)1652 void AccessibilityManager::SetSwitchAccessKeysForTest(
1653 const std::vector<int>& keys) {
1654 // Sets all keys to "Select" command.
1655 ListPrefUpdate update(profile_->GetPrefs(),
1656 ash::prefs::kAccessibilitySwitchAccessSelectKeyCodes);
1657 for (int key : keys)
1658 update->AppendInteger(key);
1659
1660 profile_->GetPrefs()->CommitPendingWrite();
1661 }
1662
1663 } // namespace chromeos
1664