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 "ash/audio/sounds.h"
8 #include "ash/autoclick/autoclick_controller.h"
9 #include "ash/high_contrast/high_contrast_controller.h"
10 #include "ash/metrics/user_metrics_recorder.h"
11 #include "ash/session/session_state_delegate.h"
12 #include "ash/shell.h"
13 #include "ash/sticky_keys/sticky_keys_controller.h"
14 #include "ash/system/tray/system_tray_notifier.h"
15 #include "base/callback.h"
16 #include "base/callback_helpers.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/memory/singleton.h"
19 #include "base/metrics/histogram.h"
20 #include "base/path_service.h"
21 #include "base/prefs/pref_member.h"
22 #include "base/prefs/pref_service.h"
23 #include "base/strings/string_split.h"
24 #include "base/strings/string_util.h"
25 #include "base/time/time.h"
26 #include "base/values.h"
27 #include "chrome/browser/accessibility/accessibility_extension_api.h"
28 #include "chrome/browser/browser_process.h"
29 #include "chrome/browser/chrome_notification_types.h"
30 #include "chrome/browser/chromeos/accessibility/magnification_manager.h"
31 #include "chrome/browser/chromeos/login/lock/screen_locker.h"
32 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
33 #include "chrome/browser/chromeos/login/ui/login_display_host_impl.h"
34 #include "chrome/browser/chromeos/login/ui/webui_login_view.h"
35 #include "chrome/browser/chromeos/login/users/user_manager.h"
36 #include "chrome/browser/chromeos/profiles/profile_helper.h"
37 #include "chrome/browser/extensions/component_loader.h"
38 #include "chrome/browser/extensions/extension_service.h"
39 #include "chrome/browser/profiles/profile.h"
40 #include "chrome/browser/profiles/profile_manager.h"
41 #include "chrome/common/chrome_paths.h"
42 #include "chrome/common/extensions/api/accessibility_private.h"
43 #include "chrome/common/extensions/extension_constants.h"
44 #include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h"
45 #include "chrome/common/pref_names.h"
46 #include "chromeos/audio/chromeos_sounds.h"
47 #include "chromeos/ime/input_method_manager.h"
48 #include "chromeos/login/login_state.h"
49 #include "content/public/browser/browser_accessibility_state.h"
50 #include "content/public/browser/browser_thread.h"
51 #include "content/public/browser/notification_details.h"
52 #include "content/public/browser/notification_service.h"
53 #include "content/public/browser/notification_source.h"
54 #include "content/public/browser/render_process_host.h"
55 #include "content/public/browser/render_view_host.h"
56 #include "content/public/browser/web_contents.h"
57 #include "content/public/browser/web_ui.h"
58 #include "extensions/browser/extension_system.h"
59 #include "extensions/browser/file_reader.h"
60 #include "extensions/common/extension.h"
61 #include "extensions/common/extension_messages.h"
62 #include "extensions/common/extension_resource.h"
63 #include "grit/browser_resources.h"
64 #include "grit/generated_resources.h"
65 #include "media/audio/sounds/sounds_manager.h"
66 #include "ui/base/l10n/l10n_util.h"
67 #include "ui/base/resource/resource_bundle.h"
68 #include "ui/keyboard/keyboard_controller.h"
69 #include "ui/keyboard/keyboard_util.h"
70
71 using content::BrowserThread;
72 using content::RenderViewHost;
73 using extensions::api::braille_display_private::BrailleController;
74 using extensions::api::braille_display_private::DisplayState;
75 using extensions::api::braille_display_private::KeyEvent;
76
77 namespace chromeos {
78
79 namespace {
80
81 static chromeos::AccessibilityManager* g_accessibility_manager = NULL;
82
83 static BrailleController* g_braille_controller_for_test = NULL;
84
GetBrailleController()85 BrailleController* GetBrailleController() {
86 return g_braille_controller_for_test
87 ? g_braille_controller_for_test
88 : BrailleController::GetInstance();
89 }
90
GetChromeVoxPath()91 base::FilePath GetChromeVoxPath() {
92 base::FilePath path;
93 if (!PathService::Get(chrome::DIR_RESOURCES, &path))
94 NOTREACHED();
95 path = path.Append(extension_misc::kChromeVoxExtensionPath);
96 return path;
97 }
98
99 // Helper class that directly loads an extension's content scripts into
100 // all of the frames corresponding to a given RenderViewHost.
101 class ContentScriptLoader {
102 public:
103 // Initialize the ContentScriptLoader with the ID of the extension
104 // and the RenderViewHost where the scripts should be loaded.
ContentScriptLoader(const std::string & extension_id,int render_process_id,int render_view_id)105 ContentScriptLoader(const std::string& extension_id,
106 int render_process_id,
107 int render_view_id)
108 : extension_id_(extension_id),
109 render_process_id_(render_process_id),
110 render_view_id_(render_view_id) {}
111
112 // Call this once with the ExtensionResource corresponding to each
113 // content script to be loaded.
AppendScript(extensions::ExtensionResource resource)114 void AppendScript(extensions::ExtensionResource resource) {
115 resources_.push(resource);
116 }
117
118 // Finally, call this method once to fetch all of the resources and
119 // load them. This method will delete this object when done.
Run()120 void Run() {
121 if (resources_.empty()) {
122 delete this;
123 return;
124 }
125
126 extensions::ExtensionResource resource = resources_.front();
127 resources_.pop();
128 scoped_refptr<FileReader> reader(new FileReader(resource, base::Bind(
129 &ContentScriptLoader::OnFileLoaded, base::Unretained(this))));
130 reader->Start();
131 }
132
133 private:
OnFileLoaded(bool success,const std::string & data)134 void OnFileLoaded(bool success, const std::string& data) {
135 if (success) {
136 ExtensionMsg_ExecuteCode_Params params;
137 params.request_id = 0;
138 params.extension_id = extension_id_;
139 params.is_javascript = true;
140 params.code = data;
141 params.run_at = extensions::UserScript::DOCUMENT_IDLE;
142 params.all_frames = true;
143 params.match_about_blank = false;
144 params.in_main_world = false;
145
146 RenderViewHost* render_view_host =
147 RenderViewHost::FromID(render_process_id_, render_view_id_);
148 if (render_view_host) {
149 render_view_host->Send(new ExtensionMsg_ExecuteCode(
150 render_view_host->GetRoutingID(), params));
151 }
152 }
153 Run();
154 }
155
156 std::string extension_id_;
157 int render_process_id_;
158 int render_view_id_;
159 std::queue<extensions::ExtensionResource> resources_;
160 };
161
162 void InjectChromeVoxContentScript(
163 ExtensionService* extension_service,
164 int render_process_id,
165 int render_view_id,
166 const base::Closure& done_cb);
167
LoadChromeVoxExtension(Profile * profile,RenderViewHost * render_view_host,base::Closure done_cb)168 void LoadChromeVoxExtension(
169 Profile* profile,
170 RenderViewHost* render_view_host,
171 base::Closure done_cb) {
172 ExtensionService* extension_service =
173 extensions::ExtensionSystem::Get(profile)->extension_service();
174 if (render_view_host) {
175 // Wrap the passed in callback to inject the content script.
176 done_cb = base::Bind(
177 &InjectChromeVoxContentScript,
178 extension_service,
179 render_view_host->GetProcess()->GetID(),
180 render_view_host->GetRoutingID(),
181 done_cb);
182 }
183 extension_service->component_loader()->AddChromeVoxExtension(done_cb);
184 }
185
InjectChromeVoxContentScript(ExtensionService * extension_service,int render_process_id,int render_view_id,const base::Closure & done_cb)186 void InjectChromeVoxContentScript(
187 ExtensionService* extension_service,
188 int render_process_id,
189 int render_view_id,
190 const base::Closure& done_cb) {
191 // Make sure to always run |done_cb|. ChromeVox was loaded even if we end up
192 // not injecting into this particular render view.
193 base::ScopedClosureRunner done_runner(done_cb);
194 RenderViewHost* render_view_host =
195 RenderViewHost::FromID(render_process_id, render_view_id);
196 if (!render_view_host)
197 return;
198 const extensions::Extension* extension =
199 extension_service->extensions()->GetByID(
200 extension_misc::kChromeVoxExtensionId);
201
202 // Set a flag to tell ChromeVox that it's just been enabled,
203 // so that it won't interrupt our speech feedback enabled message.
204 ExtensionMsg_ExecuteCode_Params params;
205 params.request_id = 0;
206 params.extension_id = extension->id();
207 params.is_javascript = true;
208 params.code = "window.INJECTED_AFTER_LOAD = true;";
209 params.run_at = extensions::UserScript::DOCUMENT_IDLE;
210 params.all_frames = true;
211 params.match_about_blank = false;
212 params.in_main_world = false;
213 render_view_host->Send(new ExtensionMsg_ExecuteCode(
214 render_view_host->GetRoutingID(), params));
215
216 // Inject ChromeVox' content scripts.
217 ContentScriptLoader* loader = new ContentScriptLoader(
218 extension->id(), render_view_host->GetProcess()->GetID(),
219 render_view_host->GetRoutingID());
220
221 const extensions::UserScriptList& content_scripts =
222 extensions::ContentScriptsInfo::GetContentScripts(extension);
223 for (size_t i = 0; i < content_scripts.size(); i++) {
224 const extensions::UserScript& script = content_scripts[i];
225 for (size_t j = 0; j < script.js_scripts().size(); ++j) {
226 const extensions::UserScript::File &file = script.js_scripts()[j];
227 extensions::ExtensionResource resource = extension->GetResource(
228 file.relative_path());
229 loader->AppendScript(resource);
230 }
231 }
232 loader->Run(); // It cleans itself up when done.
233 }
234
UnloadChromeVoxExtension(Profile * profile)235 void UnloadChromeVoxExtension(Profile* profile) {
236 base::FilePath path = GetChromeVoxPath();
237 ExtensionService* extension_service =
238 extensions::ExtensionSystem::Get(profile)->extension_service();
239 extension_service->component_loader()->Remove(path);
240 }
241
242 } // namespace
243
244 ///////////////////////////////////////////////////////////////////////////////
245 // AccessibilityStatusEventDetails
246
AccessibilityStatusEventDetails(AccessibilityNotificationType notification_type,bool enabled,ash::AccessibilityNotificationVisibility notify)247 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
248 AccessibilityNotificationType notification_type,
249 bool enabled,
250 ash::AccessibilityNotificationVisibility notify)
251 : notification_type(notification_type),
252 enabled(enabled),
253 magnifier_type(ash::kDefaultMagnifierType),
254 notify(notify) {}
255
AccessibilityStatusEventDetails(AccessibilityNotificationType notification_type,bool enabled,ash::MagnifierType magnifier_type,ash::AccessibilityNotificationVisibility notify)256 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
257 AccessibilityNotificationType notification_type,
258 bool enabled,
259 ash::MagnifierType magnifier_type,
260 ash::AccessibilityNotificationVisibility notify)
261 : notification_type(notification_type),
262 enabled(enabled),
263 magnifier_type(magnifier_type),
264 notify(notify) {}
265
266 ///////////////////////////////////////////////////////////////////////////////
267 //
268 // AccessibilityManager::PrefHandler
269
PrefHandler(const char * pref_path)270 AccessibilityManager::PrefHandler::PrefHandler(const char* pref_path)
271 : pref_path_(pref_path) {}
272
~PrefHandler()273 AccessibilityManager::PrefHandler::~PrefHandler() {}
274
HandleProfileChanged(Profile * previous_profile,Profile * current_profile)275 void AccessibilityManager::PrefHandler::HandleProfileChanged(
276 Profile* previous_profile, Profile* current_profile) {
277 // Returns if the current profile is null.
278 if (!current_profile)
279 return;
280
281 // If the user set a pref value on the login screen and is now starting a
282 // session with a new profile, copy the pref value to the profile.
283 if ((previous_profile &&
284 ProfileHelper::IsSigninProfile(previous_profile) &&
285 current_profile->IsNewProfile() &&
286 !ProfileHelper::IsSigninProfile(current_profile)) ||
287 // Special case for Guest mode:
288 // Guest mode launches a guest-mode browser process before session starts,
289 // so the previous profile is null.
290 (!previous_profile &&
291 current_profile->IsGuestSession())) {
292 // Returns if the pref has not been set by the user.
293 const PrefService::Preference* pref = ProfileHelper::GetSigninProfile()->
294 GetPrefs()->FindPreference(pref_path_);
295 if (!pref || !pref->IsUserControlled())
296 return;
297
298 // Copy the pref value from the signin screen.
299 const base::Value* value_on_login = pref->GetValue();
300 PrefService* user_prefs = current_profile->GetPrefs();
301 user_prefs->Set(pref_path_, *value_on_login);
302 }
303 }
304
305 ///////////////////////////////////////////////////////////////////////////////
306 //
307 // AccessibilityManager
308
309 // static
Initialize()310 void AccessibilityManager::Initialize() {
311 CHECK(g_accessibility_manager == NULL);
312 g_accessibility_manager = new AccessibilityManager();
313 }
314
315 // static
Shutdown()316 void AccessibilityManager::Shutdown() {
317 CHECK(g_accessibility_manager);
318 delete g_accessibility_manager;
319 g_accessibility_manager = NULL;
320 }
321
322 // static
Get()323 AccessibilityManager* AccessibilityManager::Get() {
324 return g_accessibility_manager;
325 }
326
AccessibilityManager()327 AccessibilityManager::AccessibilityManager()
328 : profile_(NULL),
329 chrome_vox_loaded_on_lock_screen_(false),
330 chrome_vox_loaded_on_user_screen_(false),
331 large_cursor_pref_handler_(prefs::kAccessibilityLargeCursorEnabled),
332 spoken_feedback_pref_handler_(prefs::kAccessibilitySpokenFeedbackEnabled),
333 high_contrast_pref_handler_(prefs::kAccessibilityHighContrastEnabled),
334 autoclick_pref_handler_(prefs::kAccessibilityAutoclickEnabled),
335 autoclick_delay_pref_handler_(prefs::kAccessibilityAutoclickDelayMs),
336 virtual_keyboard_pref_handler_(
337 prefs::kAccessibilityVirtualKeyboardEnabled),
338 large_cursor_enabled_(false),
339 sticky_keys_enabled_(false),
340 spoken_feedback_enabled_(false),
341 high_contrast_enabled_(false),
342 autoclick_enabled_(false),
343 autoclick_delay_ms_(ash::AutoclickController::kDefaultAutoclickDelayMs),
344 virtual_keyboard_enabled_(false),
345 spoken_feedback_notification_(ash::A11Y_NOTIFICATION_NONE),
346 weak_ptr_factory_(this),
347 should_speak_chrome_vox_announcements_on_user_screen_(true),
348 system_sounds_enabled_(false),
349 braille_display_connected_(false),
350 scoped_braille_observer_(this),
351 braille_ime_current_(false) {
352 notification_registrar_.Add(this,
353 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
354 content::NotificationService::AllSources());
355 notification_registrar_.Add(this,
356 chrome::NOTIFICATION_SESSION_STARTED,
357 content::NotificationService::AllSources());
358 notification_registrar_.Add(this,
359 chrome::NOTIFICATION_PROFILE_DESTROYED,
360 content::NotificationService::AllSources());
361 notification_registrar_.Add(this,
362 chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED,
363 content::NotificationService::AllSources());
364
365 input_method::InputMethodManager::Get()->AddObserver(this);
366
367 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
368 media::SoundsManager* manager = media::SoundsManager::Get();
369 manager->Initialize(SOUND_SHUTDOWN,
370 bundle.GetRawDataResource(IDR_SOUND_SHUTDOWN_WAV));
371 manager->Initialize(
372 SOUND_SPOKEN_FEEDBACK_ENABLED,
373 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_ENABLED_WAV));
374 manager->Initialize(
375 SOUND_SPOKEN_FEEDBACK_DISABLED,
376 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_DISABLED_WAV));
377 }
378
~AccessibilityManager()379 AccessibilityManager::~AccessibilityManager() {
380 CHECK(this == g_accessibility_manager);
381 AccessibilityStatusEventDetails details(
382 ACCESSIBILITY_MANAGER_SHUTDOWN,
383 false,
384 ash::A11Y_NOTIFICATION_NONE);
385 NotifyAccessibilityStatusChanged(details);
386 input_method::InputMethodManager::Get()->RemoveObserver(this);
387 }
388
ShouldShowAccessibilityMenu()389 bool AccessibilityManager::ShouldShowAccessibilityMenu() {
390 // If any of the loaded profiles has an accessibility feature turned on - or
391 // enforced to always show the menu - we return true to show the menu.
392 std::vector<Profile*> profiles =
393 g_browser_process->profile_manager()->GetLoadedProfiles();
394 for (std::vector<Profile*>::iterator it = profiles.begin();
395 it != profiles.end();
396 ++it) {
397 PrefService* pref_service = (*it)->GetPrefs();
398 if (pref_service->GetBoolean(prefs::kAccessibilityStickyKeysEnabled) ||
399 pref_service->GetBoolean(prefs::kAccessibilityLargeCursorEnabled) ||
400 pref_service->GetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled) ||
401 pref_service->GetBoolean(prefs::kAccessibilityHighContrastEnabled) ||
402 pref_service->GetBoolean(prefs::kAccessibilityAutoclickEnabled) ||
403 pref_service->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu) ||
404 pref_service->GetBoolean(prefs::kAccessibilityScreenMagnifierEnabled) ||
405 pref_service->GetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled))
406 return true;
407 }
408 return false;
409 }
410
ShouldEnableCursorCompositing()411 bool AccessibilityManager::ShouldEnableCursorCompositing() {
412 #if defined(OS_CHROMEOS)
413 if (!profile_)
414 return false;
415 PrefService* pref_service = profile_->GetPrefs();
416 // Enable cursor compositing when one or more of the listed accessibility
417 // features are turned on.
418 if (pref_service->GetBoolean(prefs::kAccessibilityLargeCursorEnabled) ||
419 pref_service->GetBoolean(prefs::kAccessibilityHighContrastEnabled) ||
420 pref_service->GetBoolean(prefs::kAccessibilityScreenMagnifierEnabled))
421 return true;
422 #endif
423 return false;
424 }
425
EnableLargeCursor(bool enabled)426 void AccessibilityManager::EnableLargeCursor(bool enabled) {
427 if (!profile_)
428 return;
429
430 PrefService* pref_service = profile_->GetPrefs();
431 pref_service->SetBoolean(prefs::kAccessibilityLargeCursorEnabled, enabled);
432 pref_service->CommitPendingWrite();
433 }
434
UpdateLargeCursorFromPref()435 void AccessibilityManager::UpdateLargeCursorFromPref() {
436 if (!profile_)
437 return;
438
439 const bool enabled =
440 profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityLargeCursorEnabled);
441
442 if (large_cursor_enabled_ == enabled)
443 return;
444
445 large_cursor_enabled_ = enabled;
446
447 AccessibilityStatusEventDetails details(
448 ACCESSIBILITY_TOGGLE_LARGE_CURSOR,
449 enabled,
450 ash::A11Y_NOTIFICATION_NONE);
451
452 NotifyAccessibilityStatusChanged(details);
453
454 #if defined(USE_ASH)
455 // Large cursor is implemented only in ash.
456 ash::Shell::GetInstance()->cursor_manager()->SetCursorSet(
457 enabled ? ui::CURSOR_SET_LARGE : ui::CURSOR_SET_NORMAL);
458 #endif
459
460 #if defined(OS_CHROMEOS)
461 ash::Shell::GetInstance()->SetCursorCompositingEnabled(
462 ShouldEnableCursorCompositing());
463 #endif
464 }
465
IsIncognitoAllowed()466 bool AccessibilityManager::IsIncognitoAllowed() {
467 UserManager* user_manager = UserManager::Get();
468 // Supervised users can't create incognito-mode windows.
469 return !(user_manager->IsLoggedInAsLocallyManagedUser());
470 }
471
IsLargeCursorEnabled()472 bool AccessibilityManager::IsLargeCursorEnabled() {
473 return large_cursor_enabled_;
474 }
475
EnableStickyKeys(bool enabled)476 void AccessibilityManager::EnableStickyKeys(bool enabled) {
477 if (!profile_)
478 return;
479 PrefService* pref_service = profile_->GetPrefs();
480 pref_service->SetBoolean(prefs::kAccessibilityStickyKeysEnabled, enabled);
481 pref_service->CommitPendingWrite();
482 }
483
IsStickyKeysEnabled()484 bool AccessibilityManager::IsStickyKeysEnabled() {
485 return sticky_keys_enabled_;
486 }
487
UpdateStickyKeysFromPref()488 void AccessibilityManager::UpdateStickyKeysFromPref() {
489 if (!profile_)
490 return;
491
492 const bool enabled =
493 profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityStickyKeysEnabled);
494
495 if (sticky_keys_enabled_ == enabled)
496 return;
497
498 sticky_keys_enabled_ = enabled;
499 #if defined(USE_ASH)
500 // Sticky keys is implemented only in ash.
501 ash::Shell::GetInstance()->sticky_keys_controller()->Enable(enabled);
502 #endif
503 }
504
EnableSpokenFeedback(bool enabled,ash::AccessibilityNotificationVisibility notify)505 void AccessibilityManager::EnableSpokenFeedback(
506 bool enabled,
507 ash::AccessibilityNotificationVisibility notify) {
508 if (!profile_)
509 return;
510
511 ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(
512 enabled ? ash::UMA_STATUS_AREA_ENABLE_SPOKEN_FEEDBACK
513 : ash::UMA_STATUS_AREA_DISABLE_SPOKEN_FEEDBACK);
514
515 spoken_feedback_notification_ = notify;
516
517 PrefService* pref_service = profile_->GetPrefs();
518 pref_service->SetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled, enabled);
519 pref_service->CommitPendingWrite();
520
521 spoken_feedback_notification_ = ash::A11Y_NOTIFICATION_NONE;
522 }
523
UpdateSpokenFeedbackFromPref()524 void AccessibilityManager::UpdateSpokenFeedbackFromPref() {
525 if (!profile_)
526 return;
527
528 const bool enabled = profile_->GetPrefs()->GetBoolean(
529 prefs::kAccessibilitySpokenFeedbackEnabled);
530
531 if (spoken_feedback_enabled_ == enabled)
532 return;
533
534 spoken_feedback_enabled_ = enabled;
535
536 ExtensionAccessibilityEventRouter::GetInstance()->
537 SetAccessibilityEnabled(enabled);
538
539 AccessibilityStatusEventDetails details(
540 ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK,
541 enabled,
542 spoken_feedback_notification_);
543
544 NotifyAccessibilityStatusChanged(details);
545
546 if (enabled) {
547 LoadChromeVox();
548 } else {
549 UnloadChromeVox();
550 }
551 UpdateBrailleImeState();
552 }
553
LoadChromeVox()554 void AccessibilityManager::LoadChromeVox() {
555 base::Closure done_cb = base::Bind(&AccessibilityManager::PostLoadChromeVox,
556 weak_ptr_factory_.GetWeakPtr(),
557 profile_);
558 ScreenLocker* screen_locker = ScreenLocker::default_screen_locker();
559 if (screen_locker && screen_locker->locked()) {
560 // If on the lock screen, loads ChromeVox only to the lock screen as for
561 // now. On unlock, it will be loaded to the user screen.
562 // (see. AccessibilityManager::Observe())
563 LoadChromeVoxToLockScreen(done_cb);
564 } else {
565 LoadChromeVoxToUserScreen(done_cb);
566 }
567 }
568
LoadChromeVoxToUserScreen(const base::Closure & done_cb)569 void AccessibilityManager::LoadChromeVoxToUserScreen(
570 const base::Closure& done_cb) {
571 if (chrome_vox_loaded_on_user_screen_)
572 return;
573
574 // Determine whether an OOBE screen is currently being shown. If so,
575 // ChromeVox will be injected directly into that screen.
576 content::WebUI* login_web_ui = NULL;
577
578 if (ProfileHelper::IsSigninProfile(profile_)) {
579 LoginDisplayHost* login_display_host = LoginDisplayHostImpl::default_host();
580 if (login_display_host) {
581 WebUILoginView* web_ui_login_view =
582 login_display_host->GetWebUILoginView();
583 if (web_ui_login_view)
584 login_web_ui = web_ui_login_view->GetWebUI();
585 }
586
587 // Lock screen uses the signin progile.
588 chrome_vox_loaded_on_lock_screen_ = true;
589 }
590
591 chrome_vox_loaded_on_user_screen_ = true;
592 LoadChromeVoxExtension(
593 profile_, login_web_ui ?
594 login_web_ui->GetWebContents()->GetRenderViewHost() : NULL,
595 done_cb);
596 }
597
LoadChromeVoxToLockScreen(const base::Closure & done_cb)598 void AccessibilityManager::LoadChromeVoxToLockScreen(
599 const base::Closure& done_cb) {
600 if (chrome_vox_loaded_on_lock_screen_)
601 return;
602
603 ScreenLocker* screen_locker = ScreenLocker::default_screen_locker();
604 if (screen_locker && screen_locker->locked()) {
605 content::WebUI* lock_web_ui = screen_locker->GetAssociatedWebUI();
606 if (lock_web_ui) {
607 Profile* profile = Profile::FromWebUI(lock_web_ui);
608 chrome_vox_loaded_on_lock_screen_ = true;
609 LoadChromeVoxExtension(
610 profile,
611 lock_web_ui->GetWebContents()->GetRenderViewHost(),
612 done_cb);
613 }
614 }
615 }
616
UnloadChromeVox()617 void AccessibilityManager::UnloadChromeVox() {
618 if (chrome_vox_loaded_on_lock_screen_)
619 UnloadChromeVoxFromLockScreen();
620
621 if (chrome_vox_loaded_on_user_screen_) {
622 UnloadChromeVoxExtension(profile_);
623 chrome_vox_loaded_on_user_screen_ = false;
624 }
625
626 PostUnloadChromeVox(profile_);
627 }
628
UnloadChromeVoxFromLockScreen()629 void AccessibilityManager::UnloadChromeVoxFromLockScreen() {
630 // Lock screen uses the signin progile.
631 Profile* signin_profile = ProfileHelper::GetSigninProfile();
632 UnloadChromeVoxExtension(signin_profile);
633 chrome_vox_loaded_on_lock_screen_ = false;
634 }
635
IsSpokenFeedbackEnabled()636 bool AccessibilityManager::IsSpokenFeedbackEnabled() {
637 return spoken_feedback_enabled_;
638 }
639
ToggleSpokenFeedback(ash::AccessibilityNotificationVisibility notify)640 void AccessibilityManager::ToggleSpokenFeedback(
641 ash::AccessibilityNotificationVisibility notify) {
642 EnableSpokenFeedback(!IsSpokenFeedbackEnabled(), notify);
643 }
644
EnableHighContrast(bool enabled)645 void AccessibilityManager::EnableHighContrast(bool enabled) {
646 if (!profile_)
647 return;
648
649 PrefService* pref_service = profile_->GetPrefs();
650 pref_service->SetBoolean(prefs::kAccessibilityHighContrastEnabled, enabled);
651 pref_service->CommitPendingWrite();
652 }
653
UpdateHighContrastFromPref()654 void AccessibilityManager::UpdateHighContrastFromPref() {
655 if (!profile_)
656 return;
657
658 const bool enabled = profile_->GetPrefs()->GetBoolean(
659 prefs::kAccessibilityHighContrastEnabled);
660
661 if (high_contrast_enabled_ == enabled)
662 return;
663
664 high_contrast_enabled_ = enabled;
665
666 AccessibilityStatusEventDetails details(
667 ACCESSIBILITY_TOGGLE_HIGH_CONTRAST_MODE,
668 enabled,
669 ash::A11Y_NOTIFICATION_NONE);
670
671 NotifyAccessibilityStatusChanged(details);
672
673 #if defined(USE_ASH)
674 ash::Shell::GetInstance()->high_contrast_controller()->SetEnabled(enabled);
675 #endif
676
677 #if defined(OS_CHROMEOS)
678 ash::Shell::GetInstance()->SetCursorCompositingEnabled(
679 ShouldEnableCursorCompositing());
680 #endif
681 }
682
OnLocaleChanged()683 void AccessibilityManager::OnLocaleChanged() {
684 if (!profile_)
685 return;
686
687 if (!IsSpokenFeedbackEnabled())
688 return;
689
690 // If the system locale changes and spoken feedback is enabled,
691 // reload ChromeVox so that it switches its internal translations
692 // to the new language.
693 EnableSpokenFeedback(false, ash::A11Y_NOTIFICATION_NONE);
694 EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_NONE);
695 }
696
IsHighContrastEnabled()697 bool AccessibilityManager::IsHighContrastEnabled() {
698 return high_contrast_enabled_;
699 }
700
EnableAutoclick(bool enabled)701 void AccessibilityManager::EnableAutoclick(bool enabled) {
702 if (!profile_)
703 return;
704
705 PrefService* pref_service = profile_->GetPrefs();
706 pref_service->SetBoolean(prefs::kAccessibilityAutoclickEnabled, enabled);
707 pref_service->CommitPendingWrite();
708 }
709
IsAutoclickEnabled()710 bool AccessibilityManager::IsAutoclickEnabled() {
711 return autoclick_enabled_;
712 }
713
UpdateAutoclickFromPref()714 void AccessibilityManager::UpdateAutoclickFromPref() {
715 bool enabled =
716 profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityAutoclickEnabled);
717
718 if (autoclick_enabled_ == enabled)
719 return;
720 autoclick_enabled_ = enabled;
721
722 #if defined(USE_ASH)
723 ash::Shell::GetInstance()->autoclick_controller()->SetEnabled(enabled);
724 #endif
725 }
726
SetAutoclickDelay(int delay_ms)727 void AccessibilityManager::SetAutoclickDelay(int delay_ms) {
728 if (!profile_)
729 return;
730
731 PrefService* pref_service = profile_->GetPrefs();
732 pref_service->SetInteger(prefs::kAccessibilityAutoclickDelayMs, delay_ms);
733 pref_service->CommitPendingWrite();
734 }
735
GetAutoclickDelay() const736 int AccessibilityManager::GetAutoclickDelay() const {
737 return autoclick_delay_ms_;
738 }
739
UpdateAutoclickDelayFromPref()740 void AccessibilityManager::UpdateAutoclickDelayFromPref() {
741 int autoclick_delay_ms =
742 profile_->GetPrefs()->GetInteger(prefs::kAccessibilityAutoclickDelayMs);
743
744 if (autoclick_delay_ms == autoclick_delay_ms_)
745 return;
746 autoclick_delay_ms_ = autoclick_delay_ms;
747
748 #if defined(USE_ASH)
749 ash::Shell::GetInstance()->autoclick_controller()->SetAutoclickDelay(
750 autoclick_delay_ms_);
751 #endif
752 }
753
EnableVirtualKeyboard(bool enabled)754 void AccessibilityManager::EnableVirtualKeyboard(bool enabled) {
755 if (!profile_)
756 return;
757
758 PrefService* pref_service = profile_->GetPrefs();
759 pref_service->SetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled,
760 enabled);
761 pref_service->CommitPendingWrite();
762 }
763
IsVirtualKeyboardEnabled()764 bool AccessibilityManager::IsVirtualKeyboardEnabled() {
765 return virtual_keyboard_enabled_;
766 }
767
UpdateVirtualKeyboardFromPref()768 void AccessibilityManager::UpdateVirtualKeyboardFromPref() {
769 if (!profile_)
770 return;
771
772 const bool enabled = profile_->GetPrefs()->GetBoolean(
773 prefs::kAccessibilityVirtualKeyboardEnabled);
774
775 if (virtual_keyboard_enabled_ == enabled)
776 return;
777 virtual_keyboard_enabled_ = enabled;
778
779 AccessibilityStatusEventDetails details(
780 ACCESSIBILITY_TOGGLE_VIRTUAL_KEYBOARD,
781 enabled,
782 ash::A11Y_NOTIFICATION_NONE);
783
784 NotifyAccessibilityStatusChanged(details);
785
786 #if defined(USE_ASH)
787 keyboard::SetAccessibilityKeyboardEnabled(enabled);
788 // Note that there are two versions of the on-screen keyboard. A full layout
789 // is provided for accessibility, which includes sticky modifier keys to
790 // enable typing of hotkeys. A compact version is used in touchview mode
791 // to provide a layout with larger keys to facilitate touch typing. In the
792 // event that the a11y keyboard is being disabled, an on-screen keyboard might
793 // still be enabled and a forced reset is required to pick up the layout
794 // change.
795 if (keyboard::IsKeyboardEnabled())
796 ash::Shell::GetInstance()->CreateKeyboard();
797 else
798 ash::Shell::GetInstance()->DeactivateKeyboard();
799 #endif
800 }
801
IsBrailleDisplayConnected() const802 bool AccessibilityManager::IsBrailleDisplayConnected() const {
803 return braille_display_connected_;
804 }
805
CheckBrailleState()806 void AccessibilityManager::CheckBrailleState() {
807 BrailleController* braille_controller = GetBrailleController();
808 if (!scoped_braille_observer_.IsObserving(braille_controller))
809 scoped_braille_observer_.Add(braille_controller);
810 BrowserThread::PostTaskAndReplyWithResult(
811 BrowserThread::IO,
812 FROM_HERE,
813 base::Bind(&BrailleController::GetDisplayState,
814 base::Unretained(braille_controller)),
815 base::Bind(&AccessibilityManager::ReceiveBrailleDisplayState,
816 weak_ptr_factory_.GetWeakPtr()));
817 }
818
ReceiveBrailleDisplayState(scoped_ptr<extensions::api::braille_display_private::DisplayState> state)819 void AccessibilityManager::ReceiveBrailleDisplayState(
820 scoped_ptr<extensions::api::braille_display_private::DisplayState> state) {
821 OnBrailleDisplayStateChanged(*state);
822 }
823
UpdateBrailleImeState()824 void AccessibilityManager::UpdateBrailleImeState() {
825 if (!profile_)
826 return;
827 PrefService* pref_service = profile_->GetPrefs();
828 std::vector<std::string> preload_engines;
829 base::SplitString(pref_service->GetString(prefs::kLanguagePreloadEngines),
830 ',',
831 &preload_engines);
832 std::vector<std::string>::iterator it =
833 std::find(preload_engines.begin(),
834 preload_engines.end(),
835 extension_misc::kBrailleImeEngineId);
836 bool is_enabled = (it != preload_engines.end());
837 bool should_be_enabled =
838 (spoken_feedback_enabled_ && braille_display_connected_);
839 if (is_enabled == should_be_enabled)
840 return;
841 if (should_be_enabled)
842 preload_engines.push_back(extension_misc::kBrailleImeEngineId);
843 else
844 preload_engines.erase(it);
845 pref_service->SetString(prefs::kLanguagePreloadEngines,
846 JoinString(preload_engines, ','));
847 braille_ime_current_ = false;
848 }
849
850 // Overridden from InputMethodManager::Observer.
InputMethodChanged(input_method::InputMethodManager * manager,bool show_message)851 void AccessibilityManager::InputMethodChanged(
852 input_method::InputMethodManager* manager,
853 bool show_message) {
854 #if defined(USE_ASH)
855 // Sticky keys is implemented only in ash.
856 ash::Shell::GetInstance()->sticky_keys_controller()->SetModifiersEnabled(
857 manager->IsISOLevel5ShiftUsedByCurrentInputMethod(),
858 manager->IsAltGrUsedByCurrentInputMethod());
859 #endif
860 const chromeos::input_method::InputMethodDescriptor descriptor =
861 manager->GetCurrentInputMethod();
862 braille_ime_current_ =
863 (descriptor.id() == extension_misc::kBrailleImeEngineId);
864 }
865
SetProfile(Profile * profile)866 void AccessibilityManager::SetProfile(Profile* profile) {
867 pref_change_registrar_.reset();
868 local_state_pref_change_registrar_.reset();
869
870 if (profile) {
871 // TODO(yoshiki): Move following code to PrefHandler.
872 pref_change_registrar_.reset(new PrefChangeRegistrar);
873 pref_change_registrar_->Init(profile->GetPrefs());
874 pref_change_registrar_->Add(
875 prefs::kAccessibilityLargeCursorEnabled,
876 base::Bind(&AccessibilityManager::UpdateLargeCursorFromPref,
877 base::Unretained(this)));
878 pref_change_registrar_->Add(
879 prefs::kAccessibilityStickyKeysEnabled,
880 base::Bind(&AccessibilityManager::UpdateStickyKeysFromPref,
881 base::Unretained(this)));
882 pref_change_registrar_->Add(
883 prefs::kAccessibilitySpokenFeedbackEnabled,
884 base::Bind(&AccessibilityManager::UpdateSpokenFeedbackFromPref,
885 base::Unretained(this)));
886 pref_change_registrar_->Add(
887 prefs::kAccessibilityHighContrastEnabled,
888 base::Bind(&AccessibilityManager::UpdateHighContrastFromPref,
889 base::Unretained(this)));
890 pref_change_registrar_->Add(
891 prefs::kAccessibilityAutoclickEnabled,
892 base::Bind(&AccessibilityManager::UpdateAutoclickFromPref,
893 base::Unretained(this)));
894 pref_change_registrar_->Add(
895 prefs::kAccessibilityAutoclickDelayMs,
896 base::Bind(&AccessibilityManager::UpdateAutoclickDelayFromPref,
897 base::Unretained(this)));
898 pref_change_registrar_->Add(
899 prefs::kAccessibilityVirtualKeyboardEnabled,
900 base::Bind(&AccessibilityManager::UpdateVirtualKeyboardFromPref,
901 base::Unretained(this)));
902
903 local_state_pref_change_registrar_.reset(new PrefChangeRegistrar);
904 local_state_pref_change_registrar_->Init(g_browser_process->local_state());
905 local_state_pref_change_registrar_->Add(
906 prefs::kApplicationLocale,
907 base::Bind(&AccessibilityManager::OnLocaleChanged,
908 base::Unretained(this)));
909
910 content::BrowserAccessibilityState::GetInstance()->AddHistogramCallback(
911 base::Bind(
912 &AccessibilityManager::UpdateChromeOSAccessibilityHistograms,
913 base::Unretained(this)));
914 }
915
916 large_cursor_pref_handler_.HandleProfileChanged(profile_, profile);
917 spoken_feedback_pref_handler_.HandleProfileChanged(profile_, profile);
918 high_contrast_pref_handler_.HandleProfileChanged(profile_, profile);
919 autoclick_pref_handler_.HandleProfileChanged(profile_, profile);
920 autoclick_delay_pref_handler_.HandleProfileChanged(profile_, profile);
921 virtual_keyboard_pref_handler_.HandleProfileChanged(profile_, profile);
922
923 bool had_profile = (profile_ != NULL);
924 profile_ = profile;
925
926 if (!had_profile && profile)
927 CheckBrailleState();
928 else
929 UpdateBrailleImeState();
930 UpdateLargeCursorFromPref();
931 UpdateStickyKeysFromPref();
932 UpdateSpokenFeedbackFromPref();
933 UpdateHighContrastFromPref();
934 UpdateAutoclickFromPref();
935 UpdateAutoclickDelayFromPref();
936 UpdateVirtualKeyboardFromPref();
937 }
938
ActiveUserChanged(const std::string & user_id)939 void AccessibilityManager::ActiveUserChanged(const std::string& user_id) {
940 SetProfile(ProfileManager::GetActiveUserProfile());
941 }
942
SetProfileForTest(Profile * profile)943 void AccessibilityManager::SetProfileForTest(Profile* profile) {
944 SetProfile(profile);
945 }
946
SetBrailleControllerForTest(BrailleController * controller)947 void AccessibilityManager::SetBrailleControllerForTest(
948 BrailleController* controller) {
949 g_braille_controller_for_test = controller;
950 }
951
EnableSystemSounds(bool system_sounds_enabled)952 void AccessibilityManager::EnableSystemSounds(bool system_sounds_enabled) {
953 system_sounds_enabled_ = system_sounds_enabled;
954 }
955
PlayShutdownSound()956 base::TimeDelta AccessibilityManager::PlayShutdownSound() {
957 if (!system_sounds_enabled_)
958 return base::TimeDelta();
959 system_sounds_enabled_ = false;
960 if (!ash::PlaySystemSoundIfSpokenFeedback(SOUND_SHUTDOWN))
961 return base::TimeDelta();
962 return media::SoundsManager::Get()->GetDuration(SOUND_SHUTDOWN);
963 }
964
InjectChromeVox(RenderViewHost * render_view_host)965 void AccessibilityManager::InjectChromeVox(RenderViewHost* render_view_host) {
966 LoadChromeVoxExtension(profile_, render_view_host, base::Closure());
967 }
968
969 scoped_ptr<AccessibilityStatusSubscription>
RegisterCallback(const AccessibilityStatusCallback & cb)970 AccessibilityManager::RegisterCallback(
971 const AccessibilityStatusCallback& cb) {
972 return callback_list_.Add(cb);
973 }
974
NotifyAccessibilityStatusChanged(AccessibilityStatusEventDetails & details)975 void AccessibilityManager::NotifyAccessibilityStatusChanged(
976 AccessibilityStatusEventDetails& details) {
977 callback_list_.Notify(details);
978 }
979
UpdateChromeOSAccessibilityHistograms()980 void AccessibilityManager::UpdateChromeOSAccessibilityHistograms() {
981 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosSpokenFeedback",
982 IsSpokenFeedbackEnabled());
983 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosHighContrast",
984 IsHighContrastEnabled());
985 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosVirtualKeyboard",
986 IsVirtualKeyboardEnabled());
987 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosStickyKeys", IsStickyKeysEnabled());
988 if (MagnificationManager::Get()) {
989 uint32 type = MagnificationManager::Get()->IsMagnifierEnabled() ?
990 MagnificationManager::Get()->GetMagnifierType() : 0;
991 // '0' means magnifier is disabled.
992 UMA_HISTOGRAM_ENUMERATION("Accessibility.CrosScreenMagnifier",
993 type,
994 ash::kMaxMagnifierType + 1);
995 }
996 if (profile_) {
997 const PrefService* const prefs = profile_->GetPrefs();
998 UMA_HISTOGRAM_BOOLEAN(
999 "Accessibility.CrosLargeCursor",
1000 prefs->GetBoolean(prefs::kAccessibilityLargeCursorEnabled));
1001 UMA_HISTOGRAM_BOOLEAN(
1002 "Accessibility.CrosAlwaysShowA11yMenu",
1003 prefs->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu));
1004
1005 bool autoclick_enabled =
1006 prefs->GetBoolean(prefs::kAccessibilityAutoclickEnabled);
1007 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosAutoclick", autoclick_enabled);
1008 if (autoclick_enabled) {
1009 // We only want to log the autoclick delay if the user has actually
1010 // enabled autoclick.
1011 UMA_HISTOGRAM_CUSTOM_TIMES(
1012 "Accessibility.CrosAutoclickDelay",
1013 base::TimeDelta::FromMilliseconds(
1014 prefs->GetInteger(prefs::kAccessibilityAutoclickDelayMs)),
1015 base::TimeDelta::FromMilliseconds(1),
1016 base::TimeDelta::FromMilliseconds(3000),
1017 50);
1018 }
1019 }
1020 }
1021
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)1022 void AccessibilityManager::Observe(
1023 int type,
1024 const content::NotificationSource& source,
1025 const content::NotificationDetails& details) {
1026 switch (type) {
1027 case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE: {
1028 // Update |profile_| when entering the login screen.
1029 Profile* profile = ProfileManager::GetActiveUserProfile();
1030 if (ProfileHelper::IsSigninProfile(profile))
1031 SetProfile(profile);
1032 break;
1033 }
1034 case chrome::NOTIFICATION_SESSION_STARTED:
1035 // Update |profile_| when entering a session.
1036 SetProfile(ProfileManager::GetActiveUserProfile());
1037
1038 // Ensure ChromeVox makes announcements at the start of new sessions.
1039 should_speak_chrome_vox_announcements_on_user_screen_ = true;
1040
1041 // Add a session state observer to be able to monitor session changes.
1042 if (!session_state_observer_.get() && ash::Shell::HasInstance())
1043 session_state_observer_.reset(
1044 new ash::ScopedSessionStateObserver(this));
1045 break;
1046 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
1047 // Update |profile_| when exiting a session or shutting down.
1048 Profile* profile = content::Source<Profile>(source).ptr();
1049 if (profile_ == profile)
1050 SetProfile(NULL);
1051 break;
1052 }
1053 case chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED: {
1054 bool is_screen_locked = *content::Details<bool>(details).ptr();
1055 if (spoken_feedback_enabled_) {
1056 if (is_screen_locked)
1057 LoadChromeVoxToLockScreen(base::Closure());
1058 // If spoken feedback was enabled, make sure it is also enabled on
1059 // the user screen.
1060 // The status tray gets verbalized by user screen ChromeVox, so we need
1061 // to load it on the user screen even if the screen is locked.
1062 LoadChromeVoxToUserScreen(base::Closure());
1063 }
1064 break;
1065 }
1066 }
1067 }
1068
OnBrailleDisplayStateChanged(const DisplayState & display_state)1069 void AccessibilityManager::OnBrailleDisplayStateChanged(
1070 const DisplayState& display_state) {
1071 braille_display_connected_ = display_state.available;
1072 if (braille_display_connected_) {
1073 EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_SHOW);
1074 }
1075 UpdateBrailleImeState();
1076
1077 AccessibilityStatusEventDetails details(
1078 ACCESSIBILITY_BRAILLE_DISPLAY_CONNECTION_STATE_CHANGED,
1079 braille_display_connected_,
1080 ash::A11Y_NOTIFICATION_SHOW);
1081 NotifyAccessibilityStatusChanged(details);
1082 }
1083
OnBrailleKeyEvent(const KeyEvent & event)1084 void AccessibilityManager::OnBrailleKeyEvent(const KeyEvent& event) {
1085 // Ensure the braille IME is active on braille keyboard (dots) input.
1086 if ((event.command ==
1087 extensions::api::braille_display_private::KEY_COMMAND_DOTS) &&
1088 !braille_ime_current_) {
1089 input_method::InputMethodManager::Get()->ChangeInputMethod(
1090 extension_misc::kBrailleImeEngineId);
1091 }
1092 }
1093
PostLoadChromeVox(Profile * profile)1094 void AccessibilityManager::PostLoadChromeVox(Profile* profile) {
1095 // Do any setup work needed immediately after ChromeVox actually loads.
1096 if (system_sounds_enabled_)
1097 ash::PlaySystemSoundAlways(SOUND_SPOKEN_FEEDBACK_ENABLED);
1098
1099 ExtensionAccessibilityEventRouter::GetInstance()->
1100 OnChromeVoxLoadStateChanged(profile_,
1101 IsSpokenFeedbackEnabled(),
1102 chrome_vox_loaded_on_lock_screen_ ||
1103 should_speak_chrome_vox_announcements_on_user_screen_);
1104
1105 should_speak_chrome_vox_announcements_on_user_screen_ =
1106 chrome_vox_loaded_on_lock_screen_;
1107 }
1108
PostUnloadChromeVox(Profile * profile)1109 void AccessibilityManager::PostUnloadChromeVox(Profile* profile) {
1110 // Do any teardown work needed immediately after ChromeVox actually unloads.
1111 if (system_sounds_enabled_)
1112 ash::PlaySystemSoundAlways(SOUND_SPOKEN_FEEDBACK_DISABLED);
1113 }
1114
1115 } // namespace chromeos
1116