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.
5 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
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/profiles/profile_helper.h"
36 #include "chrome/browser/extensions/component_loader.h"
37 #include "chrome/browser/extensions/extension_service.h"
38 #include "chrome/browser/profiles/profile.h"
39 #include "chrome/browser/profiles/profile_manager.h"
40 #include "chrome/common/chrome_paths.h"
41 #include "chrome/common/extensions/api/accessibility_private.h"
42 #include "chrome/common/extensions/extension_constants.h"
43 #include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h"
44 #include "chrome/common/pref_names.h"
45 #include "chrome/grit/browser_resources.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 "components/user_manager/user_manager.h"
50 #include "content/public/browser/browser_accessibility_state.h"
51 #include "content/public/browser/browser_thread.h"
52 #include "content/public/browser/notification_details.h"
53 #include "content/public/browser/notification_service.h"
54 #include "content/public/browser/notification_source.h"
55 #include "content/public/browser/render_process_host.h"
56 #include "content/public/browser/render_view_host.h"
57 #include "content/public/browser/web_contents.h"
58 #include "content/public/browser/web_ui.h"
59 #include "extensions/browser/extension_system.h"
60 #include "extensions/browser/file_reader.h"
61 #include "extensions/common/extension.h"
62 #include "extensions/common/extension_messages.h"
63 #include "extensions/common/extension_resource.h"
64 #include "media/audio/sounds/sounds_manager.h"
65 #include "ui/base/resource/resource_bundle.h"
66 #include "ui/keyboard/keyboard_controller.h"
67 #include "ui/keyboard/keyboard_util.h"
69 using content::BrowserThread;
70 using content::RenderViewHost;
71 using extensions::api::braille_display_private::BrailleController;
72 using extensions::api::braille_display_private::DisplayState;
73 using extensions::api::braille_display_private::KeyEvent;
79 static chromeos::AccessibilityManager* g_accessibility_manager = NULL;
81 static BrailleController* g_braille_controller_for_test = NULL;
83 BrailleController* GetBrailleController() {
84 return g_braille_controller_for_test
85 ? g_braille_controller_for_test
86 : BrailleController::GetInstance();
89 base::FilePath GetChromeVoxPath() {
91 if (!PathService::Get(chrome::DIR_RESOURCES, &path))
93 path = path.Append(extension_misc::kChromeVoxExtensionPath);
97 // Helper class that directly loads an extension's content scripts into
98 // all of the frames corresponding to a given RenderViewHost.
99 class ContentScriptLoader {
101 // Initialize the ContentScriptLoader with the ID of the extension
102 // and the RenderViewHost where the scripts should be loaded.
103 ContentScriptLoader(const std::string& extension_id,
104 int render_process_id,
106 : extension_id_(extension_id),
107 render_process_id_(render_process_id),
108 render_view_id_(render_view_id) {}
110 // Call this once with the ExtensionResource corresponding to each
111 // content script to be loaded.
112 void AppendScript(extensions::ExtensionResource resource) {
113 resources_.push(resource);
116 // Finally, call this method once to fetch all of the resources and
117 // load them. This method will delete this object when done.
119 if (resources_.empty()) {
124 extensions::ExtensionResource resource = resources_.front();
126 scoped_refptr<FileReader> reader(new FileReader(resource, base::Bind(
127 &ContentScriptLoader::OnFileLoaded, base::Unretained(this))));
132 void OnFileLoaded(bool success, const std::string& data) {
134 ExtensionMsg_ExecuteCode_Params params;
135 params.request_id = 0;
136 params.extension_id = extension_id_;
137 params.is_javascript = true;
139 params.run_at = extensions::UserScript::DOCUMENT_IDLE;
140 params.all_frames = true;
141 params.match_about_blank = false;
142 params.in_main_world = false;
144 RenderViewHost* render_view_host =
145 RenderViewHost::FromID(render_process_id_, render_view_id_);
146 if (render_view_host) {
147 render_view_host->Send(new ExtensionMsg_ExecuteCode(
148 render_view_host->GetRoutingID(), params));
154 std::string extension_id_;
155 int render_process_id_;
157 std::queue<extensions::ExtensionResource> resources_;
160 void InjectChromeVoxContentScript(
161 ExtensionService* extension_service,
162 int render_process_id,
164 const base::Closure& done_cb);
166 void LoadChromeVoxExtension(
168 RenderViewHost* render_view_host,
169 base::Closure done_cb) {
170 ExtensionService* extension_service =
171 extensions::ExtensionSystem::Get(profile)->extension_service();
172 if (render_view_host) {
173 // Wrap the passed in callback to inject the content script.
174 done_cb = base::Bind(
175 &InjectChromeVoxContentScript,
177 render_view_host->GetProcess()->GetID(),
178 render_view_host->GetRoutingID(),
181 extension_service->component_loader()->AddChromeVoxExtension(done_cb);
184 void InjectChromeVoxContentScript(
185 ExtensionService* extension_service,
186 int render_process_id,
188 const base::Closure& done_cb) {
189 // Make sure to always run |done_cb|. ChromeVox was loaded even if we end up
190 // not injecting into this particular render view.
191 base::ScopedClosureRunner done_runner(done_cb);
192 RenderViewHost* render_view_host =
193 RenderViewHost::FromID(render_process_id, render_view_id);
194 if (!render_view_host)
196 const extensions::Extension* extension =
197 extension_service->extensions()->GetByID(
198 extension_misc::kChromeVoxExtensionId);
200 // Set a flag to tell ChromeVox that it's just been enabled,
201 // so that it won't interrupt our speech feedback enabled message.
202 ExtensionMsg_ExecuteCode_Params params;
203 params.request_id = 0;
204 params.extension_id = extension->id();
205 params.is_javascript = true;
206 params.code = "window.INJECTED_AFTER_LOAD = true;";
207 params.run_at = extensions::UserScript::DOCUMENT_IDLE;
208 params.all_frames = true;
209 params.match_about_blank = false;
210 params.in_main_world = false;
211 render_view_host->Send(new ExtensionMsg_ExecuteCode(
212 render_view_host->GetRoutingID(), params));
214 // Inject ChromeVox' content scripts.
215 ContentScriptLoader* loader = new ContentScriptLoader(
216 extension->id(), render_view_host->GetProcess()->GetID(),
217 render_view_host->GetRoutingID());
219 const extensions::UserScriptList& content_scripts =
220 extensions::ContentScriptsInfo::GetContentScripts(extension);
221 for (size_t i = 0; i < content_scripts.size(); i++) {
222 const extensions::UserScript& script = content_scripts[i];
223 for (size_t j = 0; j < script.js_scripts().size(); ++j) {
224 const extensions::UserScript::File& file = script.js_scripts()[j];
225 extensions::ExtensionResource resource = extension->GetResource(
226 file.relative_path());
227 loader->AppendScript(resource);
230 loader->Run(); // It cleans itself up when done.
233 void UnloadChromeVoxExtension(Profile* profile) {
234 base::FilePath path = GetChromeVoxPath();
235 ExtensionService* extension_service =
236 extensions::ExtensionSystem::Get(profile)->extension_service();
237 extension_service->component_loader()->Remove(path);
242 ///////////////////////////////////////////////////////////////////////////////
243 // AccessibilityStatusEventDetails
245 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
246 AccessibilityNotificationType notification_type,
248 ash::AccessibilityNotificationVisibility notify)
249 : notification_type(notification_type),
251 magnifier_type(ash::kDefaultMagnifierType),
254 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
255 AccessibilityNotificationType notification_type,
257 ash::MagnifierType magnifier_type,
258 ash::AccessibilityNotificationVisibility notify)
259 : notification_type(notification_type),
261 magnifier_type(magnifier_type),
264 ///////////////////////////////////////////////////////////////////////////////
266 // AccessibilityManager::PrefHandler
268 AccessibilityManager::PrefHandler::PrefHandler(const char* pref_path)
269 : pref_path_(pref_path) {}
271 AccessibilityManager::PrefHandler::~PrefHandler() {}
273 void AccessibilityManager::PrefHandler::HandleProfileChanged(
274 Profile* previous_profile, Profile* current_profile) {
275 // Returns if the current profile is null.
276 if (!current_profile)
279 // If the user set a pref value on the login screen and is now starting a
280 // session with a new profile, copy the pref value to the profile.
281 if ((previous_profile &&
282 ProfileHelper::IsSigninProfile(previous_profile) &&
283 current_profile->IsNewProfile() &&
284 !ProfileHelper::IsSigninProfile(current_profile)) ||
285 // Special case for Guest mode:
286 // Guest mode launches a guest-mode browser process before session starts,
287 // so the previous profile is null.
288 (!previous_profile &&
289 current_profile->IsGuestSession())) {
290 // Returns if the pref has not been set by the user.
291 const PrefService::Preference* pref = ProfileHelper::GetSigninProfile()->
292 GetPrefs()->FindPreference(pref_path_);
293 if (!pref || !pref->IsUserControlled())
296 // Copy the pref value from the signin screen.
297 const base::Value* value_on_login = pref->GetValue();
298 PrefService* user_prefs = current_profile->GetPrefs();
299 user_prefs->Set(pref_path_, *value_on_login);
303 ///////////////////////////////////////////////////////////////////////////////
305 // AccessibilityManager
308 void AccessibilityManager::Initialize() {
309 CHECK(g_accessibility_manager == NULL);
310 g_accessibility_manager = new AccessibilityManager();
314 void AccessibilityManager::Shutdown() {
315 CHECK(g_accessibility_manager);
316 delete g_accessibility_manager;
317 g_accessibility_manager = NULL;
321 AccessibilityManager* AccessibilityManager::Get() {
322 return g_accessibility_manager;
325 AccessibilityManager::AccessibilityManager()
327 chrome_vox_loaded_on_lock_screen_(false),
328 chrome_vox_loaded_on_user_screen_(false),
329 large_cursor_pref_handler_(prefs::kAccessibilityLargeCursorEnabled),
330 spoken_feedback_pref_handler_(prefs::kAccessibilitySpokenFeedbackEnabled),
331 high_contrast_pref_handler_(prefs::kAccessibilityHighContrastEnabled),
332 autoclick_pref_handler_(prefs::kAccessibilityAutoclickEnabled),
333 autoclick_delay_pref_handler_(prefs::kAccessibilityAutoclickDelayMs),
334 virtual_keyboard_pref_handler_(
335 prefs::kAccessibilityVirtualKeyboardEnabled),
336 large_cursor_enabled_(false),
337 sticky_keys_enabled_(false),
338 spoken_feedback_enabled_(false),
339 high_contrast_enabled_(false),
340 autoclick_enabled_(false),
341 autoclick_delay_ms_(ash::AutoclickController::kDefaultAutoclickDelayMs),
342 virtual_keyboard_enabled_(false),
343 spoken_feedback_notification_(ash::A11Y_NOTIFICATION_NONE),
344 should_speak_chrome_vox_announcements_on_user_screen_(true),
345 system_sounds_enabled_(false),
346 braille_display_connected_(false),
347 scoped_braille_observer_(this),
348 braille_ime_current_(false),
349 weak_ptr_factory_(this) {
350 notification_registrar_.Add(this,
351 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
352 content::NotificationService::AllSources());
353 notification_registrar_.Add(this,
354 chrome::NOTIFICATION_SESSION_STARTED,
355 content::NotificationService::AllSources());
356 notification_registrar_.Add(this,
357 chrome::NOTIFICATION_PROFILE_DESTROYED,
358 content::NotificationService::AllSources());
359 notification_registrar_.Add(this,
360 chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED,
361 content::NotificationService::AllSources());
363 input_method::InputMethodManager::Get()->AddObserver(this);
365 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
366 media::SoundsManager* manager = media::SoundsManager::Get();
367 manager->Initialize(SOUND_SHUTDOWN,
368 bundle.GetRawDataResource(IDR_SOUND_SHUTDOWN_WAV));
370 SOUND_SPOKEN_FEEDBACK_ENABLED,
371 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_ENABLED_WAV));
373 SOUND_SPOKEN_FEEDBACK_DISABLED,
374 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_DISABLED_WAV));
375 manager->Initialize(SOUND_PASSTHROUGH,
376 bundle.GetRawDataResource(IDR_SOUND_PASSTHROUGH_WAV));
377 manager->Initialize(SOUND_EXIT_SCREEN,
378 bundle.GetRawDataResource(IDR_SOUND_EXIT_SCREEN_WAV));
379 manager->Initialize(SOUND_ENTER_SCREEN,
380 bundle.GetRawDataResource(IDR_SOUND_ENTER_SCREEN_WAV));
383 AccessibilityManager::~AccessibilityManager() {
384 CHECK(this == g_accessibility_manager);
385 AccessibilityStatusEventDetails details(
386 ACCESSIBILITY_MANAGER_SHUTDOWN,
388 ash::A11Y_NOTIFICATION_NONE);
389 NotifyAccessibilityStatusChanged(details);
390 input_method::InputMethodManager::Get()->RemoveObserver(this);
393 bool AccessibilityManager::ShouldShowAccessibilityMenu() {
394 // If any of the loaded profiles has an accessibility feature turned on - or
395 // enforced to always show the menu - we return true to show the menu.
396 std::vector<Profile*> profiles =
397 g_browser_process->profile_manager()->GetLoadedProfiles();
398 for (std::vector<Profile*>::iterator it = profiles.begin();
399 it != profiles.end();
401 PrefService* pref_service = (*it)->GetPrefs();
402 if (pref_service->GetBoolean(prefs::kAccessibilityStickyKeysEnabled) ||
403 pref_service->GetBoolean(prefs::kAccessibilityLargeCursorEnabled) ||
404 pref_service->GetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled) ||
405 pref_service->GetBoolean(prefs::kAccessibilityHighContrastEnabled) ||
406 pref_service->GetBoolean(prefs::kAccessibilityAutoclickEnabled) ||
407 pref_service->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu) ||
408 pref_service->GetBoolean(prefs::kAccessibilityScreenMagnifierEnabled) ||
409 pref_service->GetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled))
415 bool AccessibilityManager::ShouldEnableCursorCompositing() {
416 #if defined(OS_CHROMEOS)
419 PrefService* pref_service = profile_->GetPrefs();
420 // Enable cursor compositing when one or more of the listed accessibility
421 // features are turned on.
422 if (pref_service->GetBoolean(prefs::kAccessibilityLargeCursorEnabled) ||
423 pref_service->GetBoolean(prefs::kAccessibilityHighContrastEnabled) ||
424 pref_service->GetBoolean(prefs::kAccessibilityScreenMagnifierEnabled))
430 void AccessibilityManager::EnableLargeCursor(bool enabled) {
434 PrefService* pref_service = profile_->GetPrefs();
435 pref_service->SetBoolean(prefs::kAccessibilityLargeCursorEnabled, enabled);
436 pref_service->CommitPendingWrite();
439 void AccessibilityManager::UpdateLargeCursorFromPref() {
444 profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityLargeCursorEnabled);
446 if (large_cursor_enabled_ == enabled)
449 large_cursor_enabled_ = enabled;
451 AccessibilityStatusEventDetails details(
452 ACCESSIBILITY_TOGGLE_LARGE_CURSOR,
454 ash::A11Y_NOTIFICATION_NONE);
456 NotifyAccessibilityStatusChanged(details);
457 #if !defined(USE_ATHENA)
458 // crbug.com/408733 (and for all USE_ATHENA in this file)
461 // Large cursor is implemented only in ash.
462 ash::Shell::GetInstance()->cursor_manager()->SetCursorSet(
463 enabled ? ui::CURSOR_SET_LARGE : ui::CURSOR_SET_NORMAL);
466 #if defined(OS_CHROMEOS)
467 ash::Shell::GetInstance()->SetCursorCompositingEnabled(
468 ShouldEnableCursorCompositing());
471 #endif // !USE_ATHENA
474 bool AccessibilityManager::IsIncognitoAllowed() {
475 // Supervised users can't create incognito-mode windows.
476 return !(user_manager::UserManager::Get()->IsLoggedInAsSupervisedUser());
479 bool AccessibilityManager::IsLargeCursorEnabled() {
480 return large_cursor_enabled_;
483 void AccessibilityManager::EnableStickyKeys(bool enabled) {
486 PrefService* pref_service = profile_->GetPrefs();
487 pref_service->SetBoolean(prefs::kAccessibilityStickyKeysEnabled, enabled);
488 pref_service->CommitPendingWrite();
491 bool AccessibilityManager::IsStickyKeysEnabled() {
492 return sticky_keys_enabled_;
495 void AccessibilityManager::UpdateStickyKeysFromPref() {
500 profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityStickyKeysEnabled);
502 if (sticky_keys_enabled_ == enabled)
505 sticky_keys_enabled_ = enabled;
506 #if defined(USE_ASH) && !defined(USE_ATHENA)
507 ash::Shell::GetInstance()->sticky_keys_controller()->Enable(enabled);
511 void AccessibilityManager::EnableSpokenFeedback(
513 ash::AccessibilityNotificationVisibility notify) {
516 #if !defined(USE_ATHENA)
517 ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(
518 enabled ? ash::UMA_STATUS_AREA_ENABLE_SPOKEN_FEEDBACK
519 : ash::UMA_STATUS_AREA_DISABLE_SPOKEN_FEEDBACK);
522 spoken_feedback_notification_ = notify;
524 PrefService* pref_service = profile_->GetPrefs();
525 pref_service->SetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled, enabled);
526 pref_service->CommitPendingWrite();
528 spoken_feedback_notification_ = ash::A11Y_NOTIFICATION_NONE;
531 void AccessibilityManager::UpdateSpokenFeedbackFromPref() {
535 const bool enabled = profile_->GetPrefs()->GetBoolean(
536 prefs::kAccessibilitySpokenFeedbackEnabled);
538 if (spoken_feedback_enabled_ == enabled)
541 spoken_feedback_enabled_ = enabled;
543 ExtensionAccessibilityEventRouter::GetInstance()->
544 SetAccessibilityEnabled(enabled);
546 AccessibilityStatusEventDetails details(
547 ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK,
549 spoken_feedback_notification_);
551 NotifyAccessibilityStatusChanged(details);
558 UpdateBrailleImeState();
561 void AccessibilityManager::LoadChromeVox() {
562 base::Closure done_cb = base::Bind(&AccessibilityManager::PostLoadChromeVox,
563 weak_ptr_factory_.GetWeakPtr(),
565 ScreenLocker* screen_locker = ScreenLocker::default_screen_locker();
566 if (screen_locker && screen_locker->locked()) {
567 // If on the lock screen, loads ChromeVox only to the lock screen as for
568 // now. On unlock, it will be loaded to the user screen.
569 // (see. AccessibilityManager::Observe())
570 LoadChromeVoxToLockScreen(done_cb);
572 LoadChromeVoxToUserScreen(done_cb);
576 void AccessibilityManager::LoadChromeVoxToUserScreen(
577 const base::Closure& done_cb) {
578 if (chrome_vox_loaded_on_user_screen_)
581 // Determine whether an OOBE screen is currently being shown. If so,
582 // ChromeVox will be injected directly into that screen.
583 content::WebUI* login_web_ui = NULL;
585 if (ProfileHelper::IsSigninProfile(profile_)) {
586 LoginDisplayHost* login_display_host = LoginDisplayHostImpl::default_host();
587 if (login_display_host) {
588 WebUILoginView* web_ui_login_view =
589 login_display_host->GetWebUILoginView();
590 if (web_ui_login_view)
591 login_web_ui = web_ui_login_view->GetWebUI();
594 // Lock screen uses the signin progile.
595 chrome_vox_loaded_on_lock_screen_ = true;
598 chrome_vox_loaded_on_user_screen_ = true;
599 LoadChromeVoxExtension(
600 profile_, login_web_ui ?
601 login_web_ui->GetWebContents()->GetRenderViewHost() : NULL,
605 void AccessibilityManager::LoadChromeVoxToLockScreen(
606 const base::Closure& done_cb) {
607 if (chrome_vox_loaded_on_lock_screen_)
610 ScreenLocker* screen_locker = ScreenLocker::default_screen_locker();
611 if (screen_locker && screen_locker->locked()) {
612 content::WebUI* lock_web_ui = screen_locker->GetAssociatedWebUI();
614 Profile* profile = Profile::FromWebUI(lock_web_ui);
615 chrome_vox_loaded_on_lock_screen_ = true;
616 LoadChromeVoxExtension(
618 lock_web_ui->GetWebContents()->GetRenderViewHost(),
624 void AccessibilityManager::UnloadChromeVox() {
625 if (chrome_vox_loaded_on_lock_screen_)
626 UnloadChromeVoxFromLockScreen();
628 if (chrome_vox_loaded_on_user_screen_) {
629 UnloadChromeVoxExtension(profile_);
630 chrome_vox_loaded_on_user_screen_ = false;
633 PostUnloadChromeVox(profile_);
636 void AccessibilityManager::UnloadChromeVoxFromLockScreen() {
637 // Lock screen uses the signin progile.
638 Profile* signin_profile = ProfileHelper::GetSigninProfile();
639 UnloadChromeVoxExtension(signin_profile);
640 chrome_vox_loaded_on_lock_screen_ = false;
643 bool AccessibilityManager::IsSpokenFeedbackEnabled() {
644 return spoken_feedback_enabled_;
647 void AccessibilityManager::ToggleSpokenFeedback(
648 ash::AccessibilityNotificationVisibility notify) {
649 EnableSpokenFeedback(!IsSpokenFeedbackEnabled(), notify);
652 void AccessibilityManager::EnableHighContrast(bool enabled) {
656 PrefService* pref_service = profile_->GetPrefs();
657 pref_service->SetBoolean(prefs::kAccessibilityHighContrastEnabled, enabled);
658 pref_service->CommitPendingWrite();
661 void AccessibilityManager::UpdateHighContrastFromPref() {
665 const bool enabled = profile_->GetPrefs()->GetBoolean(
666 prefs::kAccessibilityHighContrastEnabled);
668 if (high_contrast_enabled_ == enabled)
671 high_contrast_enabled_ = enabled;
673 AccessibilityStatusEventDetails details(
674 ACCESSIBILITY_TOGGLE_HIGH_CONTRAST_MODE,
676 ash::A11Y_NOTIFICATION_NONE);
678 NotifyAccessibilityStatusChanged(details);
680 #if !defined(USE_ATHENA)
683 ash::Shell::GetInstance()->high_contrast_controller()->SetEnabled(enabled);
686 #if defined(OS_CHROMEOS)
687 ash::Shell::GetInstance()->SetCursorCompositingEnabled(
688 ShouldEnableCursorCompositing());
694 void AccessibilityManager::OnLocaleChanged() {
698 if (!IsSpokenFeedbackEnabled())
701 // If the system locale changes and spoken feedback is enabled,
702 // reload ChromeVox so that it switches its internal translations
703 // to the new language.
704 EnableSpokenFeedback(false, ash::A11Y_NOTIFICATION_NONE);
705 EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_NONE);
708 void AccessibilityManager::PlayEarcon(int sound_key) {
709 DCHECK(sound_key < chromeos::SOUND_COUNT);
710 ash::PlaySystemSoundIfSpokenFeedback(sound_key);
713 bool AccessibilityManager::IsHighContrastEnabled() {
714 return high_contrast_enabled_;
717 void AccessibilityManager::EnableAutoclick(bool enabled) {
721 PrefService* pref_service = profile_->GetPrefs();
722 pref_service->SetBoolean(prefs::kAccessibilityAutoclickEnabled, enabled);
723 pref_service->CommitPendingWrite();
726 bool AccessibilityManager::IsAutoclickEnabled() {
727 return autoclick_enabled_;
730 void AccessibilityManager::UpdateAutoclickFromPref() {
732 profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityAutoclickEnabled);
734 if (autoclick_enabled_ == enabled)
736 autoclick_enabled_ = enabled;
738 #if defined(USE_ASH) && !defined(USE_ATHENA)
739 ash::Shell::GetInstance()->autoclick_controller()->SetEnabled(enabled);
743 void AccessibilityManager::SetAutoclickDelay(int delay_ms) {
747 PrefService* pref_service = profile_->GetPrefs();
748 pref_service->SetInteger(prefs::kAccessibilityAutoclickDelayMs, delay_ms);
749 pref_service->CommitPendingWrite();
752 int AccessibilityManager::GetAutoclickDelay() const {
753 return autoclick_delay_ms_;
756 void AccessibilityManager::UpdateAutoclickDelayFromPref() {
757 int autoclick_delay_ms =
758 profile_->GetPrefs()->GetInteger(prefs::kAccessibilityAutoclickDelayMs);
760 if (autoclick_delay_ms == autoclick_delay_ms_)
762 autoclick_delay_ms_ = autoclick_delay_ms;
764 #if defined(USE_ASH) && !defined(USE_ATHENA)
765 ash::Shell::GetInstance()->autoclick_controller()->SetAutoclickDelay(
766 autoclick_delay_ms_);
770 void AccessibilityManager::EnableVirtualKeyboard(bool enabled) {
774 PrefService* pref_service = profile_->GetPrefs();
775 pref_service->SetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled,
777 pref_service->CommitPendingWrite();
780 bool AccessibilityManager::IsVirtualKeyboardEnabled() {
781 return virtual_keyboard_enabled_;
784 void AccessibilityManager::UpdateVirtualKeyboardFromPref() {
788 const bool enabled = profile_->GetPrefs()->GetBoolean(
789 prefs::kAccessibilityVirtualKeyboardEnabled);
791 if (virtual_keyboard_enabled_ == enabled)
793 virtual_keyboard_enabled_ = enabled;
795 AccessibilityStatusEventDetails details(
796 ACCESSIBILITY_TOGGLE_VIRTUAL_KEYBOARD,
798 ash::A11Y_NOTIFICATION_NONE);
800 NotifyAccessibilityStatusChanged(details);
802 #if defined(USE_ASH) && !defined(USE_ATHENA)
803 keyboard::SetAccessibilityKeyboardEnabled(enabled);
804 // Note that there are two versions of the on-screen keyboard. A full layout
805 // is provided for accessibility, which includes sticky modifier keys to
806 // enable typing of hotkeys. A compact version is used in touchview mode
807 // to provide a layout with larger keys to facilitate touch typing. In the
808 // event that the a11y keyboard is being disabled, an on-screen keyboard might
809 // still be enabled and a forced reset is required to pick up the layout
811 if (keyboard::IsKeyboardEnabled())
812 ash::Shell::GetInstance()->CreateKeyboard();
814 ash::Shell::GetInstance()->DeactivateKeyboard();
818 bool AccessibilityManager::IsBrailleDisplayConnected() const {
819 return braille_display_connected_;
822 void AccessibilityManager::CheckBrailleState() {
823 BrailleController* braille_controller = GetBrailleController();
824 if (!scoped_braille_observer_.IsObserving(braille_controller))
825 scoped_braille_observer_.Add(braille_controller);
826 BrowserThread::PostTaskAndReplyWithResult(
829 base::Bind(&BrailleController::GetDisplayState,
830 base::Unretained(braille_controller)),
831 base::Bind(&AccessibilityManager::ReceiveBrailleDisplayState,
832 weak_ptr_factory_.GetWeakPtr()));
835 void AccessibilityManager::ReceiveBrailleDisplayState(
836 scoped_ptr<extensions::api::braille_display_private::DisplayState> state) {
837 OnBrailleDisplayStateChanged(*state);
840 void AccessibilityManager::UpdateBrailleImeState() {
843 PrefService* pref_service = profile_->GetPrefs();
844 std::vector<std::string> preload_engines;
845 base::SplitString(pref_service->GetString(prefs::kLanguagePreloadEngines),
848 std::vector<std::string>::iterator it =
849 std::find(preload_engines.begin(),
850 preload_engines.end(),
851 extension_misc::kBrailleImeEngineId);
852 bool is_enabled = (it != preload_engines.end());
853 bool should_be_enabled =
854 (spoken_feedback_enabled_ && braille_display_connected_);
855 if (is_enabled == should_be_enabled)
857 if (should_be_enabled)
858 preload_engines.push_back(extension_misc::kBrailleImeEngineId);
860 preload_engines.erase(it);
861 pref_service->SetString(prefs::kLanguagePreloadEngines,
862 JoinString(preload_engines, ','));
863 braille_ime_current_ = false;
866 // Overridden from InputMethodManager::Observer.
867 void AccessibilityManager::InputMethodChanged(
868 input_method::InputMethodManager* manager,
870 #if defined(USE_ASH) && !defined(USE_ATHENA)
871 // Sticky keys is implemented only in ash.
872 // TODO(dpolukhin): support Athena, crbug.com/408733.
873 ash::Shell::GetInstance()->sticky_keys_controller()->SetModifiersEnabled(
874 manager->IsISOLevel5ShiftUsedByCurrentInputMethod(),
875 manager->IsAltGrUsedByCurrentInputMethod());
877 const chromeos::input_method::InputMethodDescriptor descriptor =
878 manager->GetActiveIMEState()->GetCurrentInputMethod();
879 braille_ime_current_ =
880 (descriptor.id() == extension_misc::kBrailleImeEngineId);
883 void AccessibilityManager::SetProfile(Profile* profile) {
884 pref_change_registrar_.reset();
885 local_state_pref_change_registrar_.reset();
888 // TODO(yoshiki): Move following code to PrefHandler.
889 pref_change_registrar_.reset(new PrefChangeRegistrar);
890 pref_change_registrar_->Init(profile->GetPrefs());
891 pref_change_registrar_->Add(
892 prefs::kAccessibilityLargeCursorEnabled,
893 base::Bind(&AccessibilityManager::UpdateLargeCursorFromPref,
894 base::Unretained(this)));
895 pref_change_registrar_->Add(
896 prefs::kAccessibilityStickyKeysEnabled,
897 base::Bind(&AccessibilityManager::UpdateStickyKeysFromPref,
898 base::Unretained(this)));
899 pref_change_registrar_->Add(
900 prefs::kAccessibilitySpokenFeedbackEnabled,
901 base::Bind(&AccessibilityManager::UpdateSpokenFeedbackFromPref,
902 base::Unretained(this)));
903 pref_change_registrar_->Add(
904 prefs::kAccessibilityHighContrastEnabled,
905 base::Bind(&AccessibilityManager::UpdateHighContrastFromPref,
906 base::Unretained(this)));
907 pref_change_registrar_->Add(
908 prefs::kAccessibilityAutoclickEnabled,
909 base::Bind(&AccessibilityManager::UpdateAutoclickFromPref,
910 base::Unretained(this)));
911 pref_change_registrar_->Add(
912 prefs::kAccessibilityAutoclickDelayMs,
913 base::Bind(&AccessibilityManager::UpdateAutoclickDelayFromPref,
914 base::Unretained(this)));
915 pref_change_registrar_->Add(
916 prefs::kAccessibilityVirtualKeyboardEnabled,
917 base::Bind(&AccessibilityManager::UpdateVirtualKeyboardFromPref,
918 base::Unretained(this)));
920 local_state_pref_change_registrar_.reset(new PrefChangeRegistrar);
921 local_state_pref_change_registrar_->Init(g_browser_process->local_state());
922 local_state_pref_change_registrar_->Add(
923 prefs::kApplicationLocale,
924 base::Bind(&AccessibilityManager::OnLocaleChanged,
925 base::Unretained(this)));
927 content::BrowserAccessibilityState::GetInstance()->AddHistogramCallback(
929 &AccessibilityManager::UpdateChromeOSAccessibilityHistograms,
930 base::Unretained(this)));
933 large_cursor_pref_handler_.HandleProfileChanged(profile_, profile);
934 spoken_feedback_pref_handler_.HandleProfileChanged(profile_, profile);
935 high_contrast_pref_handler_.HandleProfileChanged(profile_, profile);
936 autoclick_pref_handler_.HandleProfileChanged(profile_, profile);
937 autoclick_delay_pref_handler_.HandleProfileChanged(profile_, profile);
938 virtual_keyboard_pref_handler_.HandleProfileChanged(profile_, profile);
940 bool had_profile = (profile_ != NULL);
943 if (!had_profile && profile)
946 UpdateBrailleImeState();
947 UpdateLargeCursorFromPref();
948 UpdateStickyKeysFromPref();
949 UpdateSpokenFeedbackFromPref();
950 UpdateHighContrastFromPref();
951 UpdateAutoclickFromPref();
952 UpdateAutoclickDelayFromPref();
953 UpdateVirtualKeyboardFromPref();
956 void AccessibilityManager::ActiveUserChanged(const std::string& user_id) {
957 SetProfile(ProfileManager::GetActiveUserProfile());
960 void AccessibilityManager::SetProfileForTest(Profile* profile) {
964 void AccessibilityManager::SetBrailleControllerForTest(
965 BrailleController* controller) {
966 g_braille_controller_for_test = controller;
969 void AccessibilityManager::EnableSystemSounds(bool system_sounds_enabled) {
970 system_sounds_enabled_ = system_sounds_enabled;
973 base::TimeDelta AccessibilityManager::PlayShutdownSound() {
974 if (!system_sounds_enabled_)
975 return base::TimeDelta();
976 system_sounds_enabled_ = false;
977 #if !defined(USE_ATHENA)
978 if (!ash::PlaySystemSoundIfSpokenFeedback(SOUND_SHUTDOWN))
979 return base::TimeDelta();
981 return media::SoundsManager::Get()->GetDuration(SOUND_SHUTDOWN);
984 void AccessibilityManager::InjectChromeVox(RenderViewHost* render_view_host) {
985 LoadChromeVoxExtension(profile_, render_view_host, base::Closure());
988 scoped_ptr<AccessibilityStatusSubscription>
989 AccessibilityManager::RegisterCallback(
990 const AccessibilityStatusCallback& cb) {
991 return callback_list_.Add(cb);
994 void AccessibilityManager::NotifyAccessibilityStatusChanged(
995 AccessibilityStatusEventDetails& details) {
996 callback_list_.Notify(details);
999 void AccessibilityManager::UpdateChromeOSAccessibilityHistograms() {
1000 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosSpokenFeedback",
1001 IsSpokenFeedbackEnabled());
1002 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosHighContrast",
1003 IsHighContrastEnabled());
1004 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosVirtualKeyboard",
1005 IsVirtualKeyboardEnabled());
1006 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosStickyKeys", IsStickyKeysEnabled());
1007 if (MagnificationManager::Get()) {
1008 uint32 type = MagnificationManager::Get()->IsMagnifierEnabled() ?
1009 MagnificationManager::Get()->GetMagnifierType() : 0;
1010 // '0' means magnifier is disabled.
1011 UMA_HISTOGRAM_ENUMERATION("Accessibility.CrosScreenMagnifier",
1013 ash::kMaxMagnifierType + 1);
1016 const PrefService* const prefs = profile_->GetPrefs();
1017 UMA_HISTOGRAM_BOOLEAN(
1018 "Accessibility.CrosLargeCursor",
1019 prefs->GetBoolean(prefs::kAccessibilityLargeCursorEnabled));
1020 UMA_HISTOGRAM_BOOLEAN(
1021 "Accessibility.CrosAlwaysShowA11yMenu",
1022 prefs->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu));
1024 bool autoclick_enabled =
1025 prefs->GetBoolean(prefs::kAccessibilityAutoclickEnabled);
1026 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosAutoclick", autoclick_enabled);
1027 if (autoclick_enabled) {
1028 // We only want to log the autoclick delay if the user has actually
1029 // enabled autoclick.
1030 UMA_HISTOGRAM_CUSTOM_TIMES(
1031 "Accessibility.CrosAutoclickDelay",
1032 base::TimeDelta::FromMilliseconds(
1033 prefs->GetInteger(prefs::kAccessibilityAutoclickDelayMs)),
1034 base::TimeDelta::FromMilliseconds(1),
1035 base::TimeDelta::FromMilliseconds(3000),
1041 void AccessibilityManager::Observe(
1043 const content::NotificationSource& source,
1044 const content::NotificationDetails& details) {
1046 case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE: {
1047 // Update |profile_| when entering the login screen.
1048 Profile* profile = ProfileManager::GetActiveUserProfile();
1049 if (ProfileHelper::IsSigninProfile(profile))
1050 SetProfile(profile);
1053 case chrome::NOTIFICATION_SESSION_STARTED:
1054 // Update |profile_| when entering a session.
1055 SetProfile(ProfileManager::GetActiveUserProfile());
1057 // Ensure ChromeVox makes announcements at the start of new sessions.
1058 should_speak_chrome_vox_announcements_on_user_screen_ = true;
1060 // Add a session state observer to be able to monitor session changes.
1061 if (!session_state_observer_.get() && ash::Shell::HasInstance())
1062 session_state_observer_.reset(
1063 new ash::ScopedSessionStateObserver(this));
1065 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
1066 // Update |profile_| when exiting a session or shutting down.
1067 Profile* profile = content::Source<Profile>(source).ptr();
1068 if (profile_ == profile)
1072 case chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED: {
1073 bool is_screen_locked = *content::Details<bool>(details).ptr();
1074 if (spoken_feedback_enabled_) {
1075 if (is_screen_locked)
1076 LoadChromeVoxToLockScreen(base::Closure());
1077 // If spoken feedback was enabled, make sure it is also enabled on
1079 // The status tray gets verbalized by user screen ChromeVox, so we need
1080 // to load it on the user screen even if the screen is locked.
1081 LoadChromeVoxToUserScreen(base::Closure());
1088 void AccessibilityManager::OnBrailleDisplayStateChanged(
1089 const DisplayState& display_state) {
1090 braille_display_connected_ = display_state.available;
1091 if (braille_display_connected_) {
1092 EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_SHOW);
1094 UpdateBrailleImeState();
1096 AccessibilityStatusEventDetails details(
1097 ACCESSIBILITY_BRAILLE_DISPLAY_CONNECTION_STATE_CHANGED,
1098 braille_display_connected_,
1099 ash::A11Y_NOTIFICATION_SHOW);
1100 NotifyAccessibilityStatusChanged(details);
1103 void AccessibilityManager::OnBrailleKeyEvent(const KeyEvent& event) {
1104 // Ensure the braille IME is active on braille keyboard (dots) input.
1105 if ((event.command ==
1106 extensions::api::braille_display_private::KEY_COMMAND_DOTS) &&
1107 !braille_ime_current_) {
1108 input_method::InputMethodManager::Get()
1109 ->GetActiveIMEState()
1110 ->ChangeInputMethod(extension_misc::kBrailleImeEngineId,
1111 false /* show_message */);
1115 void AccessibilityManager::PostLoadChromeVox(Profile* profile) {
1116 // Do any setup work needed immediately after ChromeVox actually loads.
1117 if (system_sounds_enabled_)
1118 ash::PlaySystemSoundAlways(SOUND_SPOKEN_FEEDBACK_ENABLED);
1120 ExtensionAccessibilityEventRouter::GetInstance()->
1121 OnChromeVoxLoadStateChanged(profile_,
1122 IsSpokenFeedbackEnabled(),
1123 chrome_vox_loaded_on_lock_screen_ ||
1124 should_speak_chrome_vox_announcements_on_user_screen_);
1126 should_speak_chrome_vox_announcements_on_user_screen_ =
1127 chrome_vox_loaded_on_lock_screen_;
1130 void AccessibilityManager::PostUnloadChromeVox(Profile* profile) {
1131 // Do any teardown work needed immediately after ChromeVox actually unloads.
1132 if (system_sounds_enabled_)
1133 ash::PlaySystemSoundAlways(SOUND_SPOKEN_FEEDBACK_DISABLED);
1136 } // namespace chromeos