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 "base/callback.h"
8 #include "base/callback_helpers.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/memory/singleton.h"
11 #include "base/metrics/histogram.h"
12 #include "base/path_service.h"
13 #include "base/prefs/pref_member.h"
14 #include "base/prefs/pref_service.h"
15 #include "base/strings/string_split.h"
16 #include "base/strings/string_util.h"
17 #include "base/time/time.h"
18 #include "base/values.h"
19 #include "chrome/browser/accessibility/accessibility_extension_api.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/chromeos/login/lock/screen_locker.h"
23 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
24 #include "chrome/browser/chromeos/login/ui/login_display_host_impl.h"
25 #include "chrome/browser/chromeos/login/ui/webui_login_view.h"
26 #include "chrome/browser/chromeos/profiles/profile_helper.h"
27 #include "chrome/browser/chromeos/ui/accessibility_focus_ring_controller.h"
28 #include "chrome/browser/extensions/component_loader.h"
29 #include "chrome/browser/extensions/extension_service.h"
30 #include "chrome/browser/profiles/profile.h"
31 #include "chrome/browser/profiles/profile_manager.h"
32 #include "chrome/common/chrome_paths.h"
33 #include "chrome/common/extensions/api/accessibility_private.h"
34 #include "chrome/common/extensions/extension_constants.h"
35 #include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h"
36 #include "chrome/common/pref_names.h"
37 #include "chrome/grit/browser_resources.h"
38 #include "chromeos/audio/chromeos_sounds.h"
39 #include "chromeos/ime/input_method_manager.h"
40 #include "chromeos/login/login_state.h"
41 #include "components/user_manager/user_manager.h"
42 #include "content/public/browser/browser_accessibility_state.h"
43 #include "content/public/browser/browser_thread.h"
44 #include "content/public/browser/notification_details.h"
45 #include "content/public/browser/notification_service.h"
46 #include "content/public/browser/notification_source.h"
47 #include "content/public/browser/render_process_host.h"
48 #include "content/public/browser/render_view_host.h"
49 #include "content/public/browser/web_contents.h"
50 #include "content/public/browser/web_ui.h"
51 #include "extensions/browser/extension_system.h"
52 #include "extensions/browser/file_reader.h"
53 #include "extensions/common/extension.h"
54 #include "extensions/common/extension_messages.h"
55 #include "extensions/common/extension_resource.h"
56 #include "media/audio/sounds/sounds_manager.h"
57 #include "ui/base/resource/resource_bundle.h"
58 #include "ui/keyboard/keyboard_controller.h"
59 #include "ui/keyboard/keyboard_util.h"
61 #if !defined(USE_ATHENA)
62 #include "ash/audio/sounds.h"
63 #include "ash/autoclick/autoclick_controller.h"
64 #include "ash/high_contrast/high_contrast_controller.h"
65 #include "ash/metrics/user_metrics_recorder.h"
66 #include "ash/session/session_state_delegate.h"
67 #include "ash/shell.h"
68 #include "ash/sticky_keys/sticky_keys_controller.h"
69 #include "ash/system/tray/system_tray_notifier.h"
70 #include "chrome/browser/chromeos/accessibility/magnification_manager.h"
73 using content::BrowserThread;
74 using content::RenderViewHost;
75 using extensions::api::braille_display_private::BrailleController;
76 using extensions::api::braille_display_private::DisplayState;
77 using extensions::api::braille_display_private::KeyEvent;
83 static chromeos::AccessibilityManager* g_accessibility_manager = NULL;
85 static BrailleController* g_braille_controller_for_test = NULL;
87 BrailleController* GetBrailleController() {
88 return g_braille_controller_for_test
89 ? g_braille_controller_for_test
90 : BrailleController::GetInstance();
93 base::FilePath GetChromeVoxPath() {
95 if (!PathService::Get(chrome::DIR_RESOURCES, &path))
97 path = path.Append(extension_misc::kChromeVoxExtensionPath);
101 // Helper class that directly loads an extension's content scripts into
102 // all of the frames corresponding to a given RenderViewHost.
103 class ContentScriptLoader {
105 // Initialize the ContentScriptLoader with the ID of the extension
106 // and the RenderViewHost where the scripts should be loaded.
107 ContentScriptLoader(const std::string& extension_id,
108 int render_process_id,
110 : extension_id_(extension_id),
111 render_process_id_(render_process_id),
112 render_view_id_(render_view_id) {}
114 // Call this once with the ExtensionResource corresponding to each
115 // content script to be loaded.
116 void AppendScript(extensions::ExtensionResource resource) {
117 resources_.push(resource);
120 // Finally, call this method once to fetch all of the resources and
121 // load them. This method will delete this object when done.
123 if (resources_.empty()) {
128 extensions::ExtensionResource resource = resources_.front();
130 scoped_refptr<FileReader> reader(new FileReader(resource, base::Bind(
131 &ContentScriptLoader::OnFileLoaded, base::Unretained(this))));
136 void OnFileLoaded(bool success, const std::string& data) {
138 ExtensionMsg_ExecuteCode_Params params;
139 params.request_id = 0;
140 params.extension_id = extension_id_;
141 params.is_javascript = true;
143 params.run_at = extensions::UserScript::DOCUMENT_IDLE;
144 params.all_frames = true;
145 params.match_about_blank = false;
146 params.in_main_world = false;
148 RenderViewHost* render_view_host =
149 RenderViewHost::FromID(render_process_id_, render_view_id_);
150 if (render_view_host) {
151 render_view_host->Send(new ExtensionMsg_ExecuteCode(
152 render_view_host->GetRoutingID(), params));
158 std::string extension_id_;
159 int render_process_id_;
161 std::queue<extensions::ExtensionResource> resources_;
164 void InjectChromeVoxContentScript(
165 ExtensionService* extension_service,
166 int render_process_id,
168 const base::Closure& done_cb);
170 void LoadChromeVoxExtension(
172 RenderViewHost* render_view_host,
173 base::Closure done_cb) {
174 ExtensionService* extension_service =
175 extensions::ExtensionSystem::Get(profile)->extension_service();
176 if (render_view_host) {
177 // Wrap the passed in callback to inject the content script.
178 done_cb = base::Bind(
179 &InjectChromeVoxContentScript,
181 render_view_host->GetProcess()->GetID(),
182 render_view_host->GetRoutingID(),
185 extension_service->component_loader()->AddChromeVoxExtension(done_cb);
188 void InjectChromeVoxContentScript(
189 ExtensionService* extension_service,
190 int render_process_id,
192 const base::Closure& done_cb) {
193 // Make sure to always run |done_cb|. ChromeVox was loaded even if we end up
194 // not injecting into this particular render view.
195 base::ScopedClosureRunner done_runner(done_cb);
196 RenderViewHost* render_view_host =
197 RenderViewHost::FromID(render_process_id, render_view_id);
198 if (!render_view_host)
200 const extensions::Extension* extension =
201 extension_service->extensions()->GetByID(
202 extension_misc::kChromeVoxExtensionId);
204 // Set a flag to tell ChromeVox that it's just been enabled,
205 // so that it won't interrupt our speech feedback enabled message.
206 ExtensionMsg_ExecuteCode_Params params;
207 params.request_id = 0;
208 params.extension_id = extension->id();
209 params.is_javascript = true;
210 params.code = "window.INJECTED_AFTER_LOAD = true;";
211 params.run_at = extensions::UserScript::DOCUMENT_IDLE;
212 params.all_frames = true;
213 params.match_about_blank = false;
214 params.in_main_world = false;
215 render_view_host->Send(new ExtensionMsg_ExecuteCode(
216 render_view_host->GetRoutingID(), params));
218 // Inject ChromeVox' content scripts.
219 ContentScriptLoader* loader = new ContentScriptLoader(
220 extension->id(), render_view_host->GetProcess()->GetID(),
221 render_view_host->GetRoutingID());
223 const extensions::UserScriptList& content_scripts =
224 extensions::ContentScriptsInfo::GetContentScripts(extension);
225 for (size_t i = 0; i < content_scripts.size(); i++) {
226 const extensions::UserScript& script = content_scripts[i];
227 for (size_t j = 0; j < script.js_scripts().size(); ++j) {
228 const extensions::UserScript::File& file = script.js_scripts()[j];
229 extensions::ExtensionResource resource = extension->GetResource(
230 file.relative_path());
231 loader->AppendScript(resource);
234 loader->Run(); // It cleans itself up when done.
237 void UnloadChromeVoxExtension(Profile* profile) {
238 base::FilePath path = GetChromeVoxPath();
239 ExtensionService* extension_service =
240 extensions::ExtensionSystem::Get(profile)->extension_service();
241 extension_service->component_loader()->Remove(path);
246 ///////////////////////////////////////////////////////////////////////////////
247 // AccessibilityStatusEventDetails
249 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
250 AccessibilityNotificationType notification_type,
252 ui::AccessibilityNotificationVisibility notify)
253 : notification_type(notification_type),
255 magnifier_type(ui::kDefaultMagnifierType),
258 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
259 AccessibilityNotificationType notification_type,
261 ui::MagnifierType magnifier_type,
262 ui::AccessibilityNotificationVisibility notify)
263 : notification_type(notification_type),
265 magnifier_type(magnifier_type),
268 ///////////////////////////////////////////////////////////////////////////////
270 // AccessibilityManager::PrefHandler
272 AccessibilityManager::PrefHandler::PrefHandler(const char* pref_path)
273 : pref_path_(pref_path) {}
275 AccessibilityManager::PrefHandler::~PrefHandler() {}
277 void AccessibilityManager::PrefHandler::HandleProfileChanged(
278 Profile* previous_profile, Profile* current_profile) {
279 // Returns if the current profile is null.
280 if (!current_profile)
283 // If the user set a pref value on the login screen and is now starting a
284 // session with a new profile, copy the pref value to the profile.
285 if ((previous_profile &&
286 ProfileHelper::IsSigninProfile(previous_profile) &&
287 current_profile->IsNewProfile() &&
288 !ProfileHelper::IsSigninProfile(current_profile)) ||
289 // Special case for Guest mode:
290 // Guest mode launches a guest-mode browser process before session starts,
291 // so the previous profile is null.
292 (!previous_profile &&
293 current_profile->IsGuestSession())) {
294 // Returns if the pref has not been set by the user.
295 const PrefService::Preference* pref = ProfileHelper::GetSigninProfile()->
296 GetPrefs()->FindPreference(pref_path_);
297 if (!pref || !pref->IsUserControlled())
300 // Copy the pref value from the signin screen.
301 const base::Value* value_on_login = pref->GetValue();
302 PrefService* user_prefs = current_profile->GetPrefs();
303 user_prefs->Set(pref_path_, *value_on_login);
307 ///////////////////////////////////////////////////////////////////////////////
309 // AccessibilityManager
312 void AccessibilityManager::Initialize() {
313 CHECK(g_accessibility_manager == NULL);
314 g_accessibility_manager = new AccessibilityManager();
318 void AccessibilityManager::Shutdown() {
319 CHECK(g_accessibility_manager);
320 delete g_accessibility_manager;
321 g_accessibility_manager = NULL;
325 AccessibilityManager* AccessibilityManager::Get() {
326 return g_accessibility_manager;
329 AccessibilityManager::AccessibilityManager()
331 chrome_vox_loaded_on_lock_screen_(false),
332 chrome_vox_loaded_on_user_screen_(false),
333 large_cursor_pref_handler_(prefs::kAccessibilityLargeCursorEnabled),
334 spoken_feedback_pref_handler_(prefs::kAccessibilitySpokenFeedbackEnabled),
335 high_contrast_pref_handler_(prefs::kAccessibilityHighContrastEnabled),
336 autoclick_pref_handler_(prefs::kAccessibilityAutoclickEnabled),
337 autoclick_delay_pref_handler_(prefs::kAccessibilityAutoclickDelayMs),
338 virtual_keyboard_pref_handler_(
339 prefs::kAccessibilityVirtualKeyboardEnabled),
340 large_cursor_enabled_(false),
341 sticky_keys_enabled_(false),
342 spoken_feedback_enabled_(false),
343 high_contrast_enabled_(false),
344 autoclick_enabled_(false),
345 #if defined(USE_ATHENA)
346 autoclick_delay_ms_(400),
348 autoclick_delay_ms_(ash::AutoclickController::kDefaultAutoclickDelayMs),
350 virtual_keyboard_enabled_(false),
351 spoken_feedback_notification_(ui::A11Y_NOTIFICATION_NONE),
352 should_speak_chrome_vox_announcements_on_user_screen_(true),
353 system_sounds_enabled_(false),
354 braille_display_connected_(false),
355 scoped_braille_observer_(this),
356 braille_ime_current_(false),
357 weak_ptr_factory_(this) {
358 notification_registrar_.Add(this,
359 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
360 content::NotificationService::AllSources());
361 notification_registrar_.Add(this,
362 chrome::NOTIFICATION_SESSION_STARTED,
363 content::NotificationService::AllSources());
364 notification_registrar_.Add(this,
365 chrome::NOTIFICATION_PROFILE_DESTROYED,
366 content::NotificationService::AllSources());
367 notification_registrar_.Add(this,
368 chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED,
369 content::NotificationService::AllSources());
371 input_method::InputMethodManager::Get()->AddObserver(this);
373 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
374 media::SoundsManager* manager = media::SoundsManager::Get();
375 manager->Initialize(SOUND_SHUTDOWN,
376 bundle.GetRawDataResource(IDR_SOUND_SHUTDOWN_WAV));
378 SOUND_SPOKEN_FEEDBACK_ENABLED,
379 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_ENABLED_WAV));
381 SOUND_SPOKEN_FEEDBACK_DISABLED,
382 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_DISABLED_WAV));
383 manager->Initialize(SOUND_PASSTHROUGH,
384 bundle.GetRawDataResource(IDR_SOUND_PASSTHROUGH_WAV));
385 manager->Initialize(SOUND_EXIT_SCREEN,
386 bundle.GetRawDataResource(IDR_SOUND_EXIT_SCREEN_WAV));
387 manager->Initialize(SOUND_ENTER_SCREEN,
388 bundle.GetRawDataResource(IDR_SOUND_ENTER_SCREEN_WAV));
391 AccessibilityManager::~AccessibilityManager() {
392 CHECK(this == g_accessibility_manager);
393 AccessibilityStatusEventDetails details(
394 ACCESSIBILITY_MANAGER_SHUTDOWN,
396 ui::A11Y_NOTIFICATION_NONE);
397 NotifyAccessibilityStatusChanged(details);
398 input_method::InputMethodManager::Get()->RemoveObserver(this);
401 bool AccessibilityManager::ShouldShowAccessibilityMenu() {
402 // If any of the loaded profiles has an accessibility feature turned on - or
403 // enforced to always show the menu - we return true to show the menu.
404 std::vector<Profile*> profiles =
405 g_browser_process->profile_manager()->GetLoadedProfiles();
406 for (std::vector<Profile*>::iterator it = profiles.begin();
407 it != profiles.end();
409 PrefService* pref_service = (*it)->GetPrefs();
410 if (pref_service->GetBoolean(prefs::kAccessibilityStickyKeysEnabled) ||
411 pref_service->GetBoolean(prefs::kAccessibilityLargeCursorEnabled) ||
412 pref_service->GetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled) ||
413 pref_service->GetBoolean(prefs::kAccessibilityHighContrastEnabled) ||
414 pref_service->GetBoolean(prefs::kAccessibilityAutoclickEnabled) ||
415 pref_service->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu) ||
416 pref_service->GetBoolean(prefs::kAccessibilityScreenMagnifierEnabled) ||
417 pref_service->GetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled))
423 bool AccessibilityManager::ShouldEnableCursorCompositing() {
424 #if defined(OS_CHROMEOS)
427 PrefService* pref_service = profile_->GetPrefs();
428 // Enable cursor compositing when one or more of the listed accessibility
429 // features are turned on.
430 if (pref_service->GetBoolean(prefs::kAccessibilityLargeCursorEnabled) ||
431 pref_service->GetBoolean(prefs::kAccessibilityHighContrastEnabled) ||
432 pref_service->GetBoolean(prefs::kAccessibilityScreenMagnifierEnabled))
438 void AccessibilityManager::EnableLargeCursor(bool enabled) {
442 PrefService* pref_service = profile_->GetPrefs();
443 pref_service->SetBoolean(prefs::kAccessibilityLargeCursorEnabled, enabled);
444 pref_service->CommitPendingWrite();
447 void AccessibilityManager::UpdateLargeCursorFromPref() {
452 profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityLargeCursorEnabled);
454 if (large_cursor_enabled_ == enabled)
457 large_cursor_enabled_ = enabled;
459 AccessibilityStatusEventDetails details(
460 ACCESSIBILITY_TOGGLE_LARGE_CURSOR,
462 ui::A11Y_NOTIFICATION_NONE);
464 NotifyAccessibilityStatusChanged(details);
465 #if !defined(USE_ATHENA)
466 // crbug.com/408733 (and for all USE_ATHENA in this file)
469 // Large cursor is implemented only in ash.
470 ash::Shell::GetInstance()->cursor_manager()->SetCursorSet(
471 enabled ? ui::CURSOR_SET_LARGE : ui::CURSOR_SET_NORMAL);
474 #if defined(OS_CHROMEOS)
475 ash::Shell::GetInstance()->SetCursorCompositingEnabled(
476 ShouldEnableCursorCompositing());
479 #endif // !USE_ATHENA
482 bool AccessibilityManager::IsIncognitoAllowed() {
483 // Supervised users can't create incognito-mode windows.
484 return !(user_manager::UserManager::Get()->IsLoggedInAsSupervisedUser());
487 bool AccessibilityManager::IsLargeCursorEnabled() {
488 return large_cursor_enabled_;
491 void AccessibilityManager::EnableStickyKeys(bool enabled) {
494 PrefService* pref_service = profile_->GetPrefs();
495 pref_service->SetBoolean(prefs::kAccessibilityStickyKeysEnabled, enabled);
496 pref_service->CommitPendingWrite();
499 bool AccessibilityManager::IsStickyKeysEnabled() {
500 return sticky_keys_enabled_;
503 void AccessibilityManager::UpdateStickyKeysFromPref() {
508 profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityStickyKeysEnabled);
510 if (sticky_keys_enabled_ == enabled)
513 sticky_keys_enabled_ = enabled;
514 #if defined(USE_ASH) && !defined(USE_ATHENA)
515 ash::Shell::GetInstance()->sticky_keys_controller()->Enable(enabled);
519 void AccessibilityManager::EnableSpokenFeedback(
521 ui::AccessibilityNotificationVisibility notify) {
524 #if !defined(USE_ATHENA)
525 ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(
526 enabled ? ash::UMA_STATUS_AREA_ENABLE_SPOKEN_FEEDBACK
527 : ash::UMA_STATUS_AREA_DISABLE_SPOKEN_FEEDBACK);
530 spoken_feedback_notification_ = notify;
532 PrefService* pref_service = profile_->GetPrefs();
533 pref_service->SetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled, enabled);
534 pref_service->CommitPendingWrite();
536 spoken_feedback_notification_ = ui::A11Y_NOTIFICATION_NONE;
539 void AccessibilityManager::UpdateSpokenFeedbackFromPref() {
543 const bool enabled = profile_->GetPrefs()->GetBoolean(
544 prefs::kAccessibilitySpokenFeedbackEnabled);
546 if (spoken_feedback_enabled_ == enabled)
549 spoken_feedback_enabled_ = enabled;
551 ExtensionAccessibilityEventRouter::GetInstance()->
552 SetAccessibilityEnabled(enabled);
554 AccessibilityStatusEventDetails details(
555 ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK,
557 spoken_feedback_notification_);
559 NotifyAccessibilityStatusChanged(details);
566 UpdateBrailleImeState();
569 void AccessibilityManager::LoadChromeVox() {
570 base::Closure done_cb = base::Bind(&AccessibilityManager::PostLoadChromeVox,
571 weak_ptr_factory_.GetWeakPtr(),
573 ScreenLocker* screen_locker = ScreenLocker::default_screen_locker();
574 if (screen_locker && screen_locker->locked()) {
575 // If on the lock screen, loads ChromeVox only to the lock screen as for
576 // now. On unlock, it will be loaded to the user screen.
577 // (see. AccessibilityManager::Observe())
578 LoadChromeVoxToLockScreen(done_cb);
580 LoadChromeVoxToUserScreen(done_cb);
584 void AccessibilityManager::LoadChromeVoxToUserScreen(
585 const base::Closure& done_cb) {
586 if (chrome_vox_loaded_on_user_screen_)
589 // Determine whether an OOBE screen is currently being shown. If so,
590 // ChromeVox will be injected directly into that screen.
591 content::WebUI* login_web_ui = NULL;
593 if (ProfileHelper::IsSigninProfile(profile_)) {
594 LoginDisplayHost* login_display_host = LoginDisplayHostImpl::default_host();
595 if (login_display_host) {
596 WebUILoginView* web_ui_login_view =
597 login_display_host->GetWebUILoginView();
598 if (web_ui_login_view)
599 login_web_ui = web_ui_login_view->GetWebUI();
602 // Lock screen uses the signin progile.
603 chrome_vox_loaded_on_lock_screen_ = true;
606 chrome_vox_loaded_on_user_screen_ = true;
607 LoadChromeVoxExtension(
608 profile_, login_web_ui ?
609 login_web_ui->GetWebContents()->GetRenderViewHost() : NULL,
613 void AccessibilityManager::LoadChromeVoxToLockScreen(
614 const base::Closure& done_cb) {
615 if (chrome_vox_loaded_on_lock_screen_)
618 ScreenLocker* screen_locker = ScreenLocker::default_screen_locker();
619 if (screen_locker && screen_locker->locked()) {
620 content::WebUI* lock_web_ui = screen_locker->GetAssociatedWebUI();
622 Profile* profile = Profile::FromWebUI(lock_web_ui);
623 chrome_vox_loaded_on_lock_screen_ = true;
624 LoadChromeVoxExtension(
626 lock_web_ui->GetWebContents()->GetRenderViewHost(),
632 void AccessibilityManager::UnloadChromeVox() {
633 if (chrome_vox_loaded_on_lock_screen_)
634 UnloadChromeVoxFromLockScreen();
636 if (chrome_vox_loaded_on_user_screen_) {
637 UnloadChromeVoxExtension(profile_);
638 chrome_vox_loaded_on_user_screen_ = false;
641 PostUnloadChromeVox(profile_);
644 void AccessibilityManager::UnloadChromeVoxFromLockScreen() {
645 // Lock screen uses the signin progile.
646 Profile* signin_profile = ProfileHelper::GetSigninProfile();
647 UnloadChromeVoxExtension(signin_profile);
648 chrome_vox_loaded_on_lock_screen_ = false;
651 bool AccessibilityManager::IsSpokenFeedbackEnabled() {
652 return spoken_feedback_enabled_;
655 void AccessibilityManager::ToggleSpokenFeedback(
656 ui::AccessibilityNotificationVisibility notify) {
657 EnableSpokenFeedback(!IsSpokenFeedbackEnabled(), notify);
660 void AccessibilityManager::EnableHighContrast(bool enabled) {
664 PrefService* pref_service = profile_->GetPrefs();
665 pref_service->SetBoolean(prefs::kAccessibilityHighContrastEnabled, enabled);
666 pref_service->CommitPendingWrite();
669 void AccessibilityManager::UpdateHighContrastFromPref() {
673 const bool enabled = profile_->GetPrefs()->GetBoolean(
674 prefs::kAccessibilityHighContrastEnabled);
676 if (high_contrast_enabled_ == enabled)
679 high_contrast_enabled_ = enabled;
681 AccessibilityStatusEventDetails details(
682 ACCESSIBILITY_TOGGLE_HIGH_CONTRAST_MODE,
684 ui::A11Y_NOTIFICATION_NONE);
686 NotifyAccessibilityStatusChanged(details);
688 #if !defined(USE_ATHENA)
691 ash::Shell::GetInstance()->high_contrast_controller()->SetEnabled(enabled);
694 #if defined(OS_CHROMEOS)
695 ash::Shell::GetInstance()->SetCursorCompositingEnabled(
696 ShouldEnableCursorCompositing());
702 void AccessibilityManager::OnLocaleChanged() {
706 if (!IsSpokenFeedbackEnabled())
709 // If the system locale changes and spoken feedback is enabled,
710 // reload ChromeVox so that it switches its internal translations
711 // to the new language.
712 EnableSpokenFeedback(false, ui::A11Y_NOTIFICATION_NONE);
713 EnableSpokenFeedback(true, ui::A11Y_NOTIFICATION_NONE);
716 void AccessibilityManager::PlayEarcon(int sound_key) {
717 #if !defined(USE_ATHENA)
718 DCHECK(sound_key < chromeos::SOUND_COUNT);
719 ash::PlaySystemSoundIfSpokenFeedback(sound_key);
723 bool AccessibilityManager::IsHighContrastEnabled() {
724 return high_contrast_enabled_;
727 void AccessibilityManager::EnableAutoclick(bool enabled) {
731 PrefService* pref_service = profile_->GetPrefs();
732 pref_service->SetBoolean(prefs::kAccessibilityAutoclickEnabled, enabled);
733 pref_service->CommitPendingWrite();
736 bool AccessibilityManager::IsAutoclickEnabled() {
737 return autoclick_enabled_;
740 void AccessibilityManager::UpdateAutoclickFromPref() {
742 profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityAutoclickEnabled);
744 if (autoclick_enabled_ == enabled)
746 autoclick_enabled_ = enabled;
748 #if defined(USE_ASH) && !defined(USE_ATHENA)
749 ash::Shell::GetInstance()->autoclick_controller()->SetEnabled(enabled);
753 void AccessibilityManager::SetAutoclickDelay(int delay_ms) {
757 PrefService* pref_service = profile_->GetPrefs();
758 pref_service->SetInteger(prefs::kAccessibilityAutoclickDelayMs, delay_ms);
759 pref_service->CommitPendingWrite();
762 int AccessibilityManager::GetAutoclickDelay() const {
763 return autoclick_delay_ms_;
766 void AccessibilityManager::UpdateAutoclickDelayFromPref() {
767 int autoclick_delay_ms =
768 profile_->GetPrefs()->GetInteger(prefs::kAccessibilityAutoclickDelayMs);
770 if (autoclick_delay_ms == autoclick_delay_ms_)
772 autoclick_delay_ms_ = autoclick_delay_ms;
774 #if defined(USE_ASH) && !defined(USE_ATHENA)
775 ash::Shell::GetInstance()->autoclick_controller()->SetAutoclickDelay(
776 autoclick_delay_ms_);
780 void AccessibilityManager::EnableVirtualKeyboard(bool enabled) {
784 PrefService* pref_service = profile_->GetPrefs();
785 pref_service->SetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled,
787 pref_service->CommitPendingWrite();
790 bool AccessibilityManager::IsVirtualKeyboardEnabled() {
791 return virtual_keyboard_enabled_;
794 void AccessibilityManager::UpdateVirtualKeyboardFromPref() {
798 const bool enabled = profile_->GetPrefs()->GetBoolean(
799 prefs::kAccessibilityVirtualKeyboardEnabled);
801 if (virtual_keyboard_enabled_ == enabled)
803 virtual_keyboard_enabled_ = enabled;
805 #if defined(USE_ASH) && !defined(USE_ATHENA)
806 keyboard::SetAccessibilityKeyboardEnabled(enabled);
807 // Note that there are two versions of the on-screen keyboard. A full layout
808 // is provided for accessibility, which includes sticky modifier keys to
809 // enable typing of hotkeys. A compact version is used in touchview mode
810 // to provide a layout with larger keys to facilitate touch typing. In the
811 // event that the a11y keyboard is being disabled, an on-screen keyboard might
812 // still be enabled and a forced reset is required to pick up the layout
814 if (keyboard::IsKeyboardEnabled())
815 ash::Shell::GetInstance()->CreateKeyboard();
817 ash::Shell::GetInstance()->DeactivateKeyboard();
820 AccessibilityStatusEventDetails details(
821 ACCESSIBILITY_TOGGLE_VIRTUAL_KEYBOARD,
823 ui::A11Y_NOTIFICATION_NONE);
824 NotifyAccessibilityStatusChanged(details);
827 bool AccessibilityManager::IsBrailleDisplayConnected() const {
828 return braille_display_connected_;
831 void AccessibilityManager::CheckBrailleState() {
832 BrailleController* braille_controller = GetBrailleController();
833 if (!scoped_braille_observer_.IsObserving(braille_controller))
834 scoped_braille_observer_.Add(braille_controller);
835 BrowserThread::PostTaskAndReplyWithResult(
838 base::Bind(&BrailleController::GetDisplayState,
839 base::Unretained(braille_controller)),
840 base::Bind(&AccessibilityManager::ReceiveBrailleDisplayState,
841 weak_ptr_factory_.GetWeakPtr()));
844 void AccessibilityManager::ReceiveBrailleDisplayState(
845 scoped_ptr<extensions::api::braille_display_private::DisplayState> state) {
846 OnBrailleDisplayStateChanged(*state);
849 void AccessibilityManager::UpdateBrailleImeState() {
852 PrefService* pref_service = profile_->GetPrefs();
853 std::vector<std::string> preload_engines;
854 base::SplitString(pref_service->GetString(prefs::kLanguagePreloadEngines),
857 std::vector<std::string>::iterator it =
858 std::find(preload_engines.begin(),
859 preload_engines.end(),
860 extension_misc::kBrailleImeEngineId);
861 bool is_enabled = (it != preload_engines.end());
862 bool should_be_enabled =
863 (spoken_feedback_enabled_ && braille_display_connected_);
864 if (is_enabled == should_be_enabled)
866 if (should_be_enabled)
867 preload_engines.push_back(extension_misc::kBrailleImeEngineId);
869 preload_engines.erase(it);
870 pref_service->SetString(prefs::kLanguagePreloadEngines,
871 JoinString(preload_engines, ','));
872 braille_ime_current_ = false;
875 // Overridden from InputMethodManager::Observer.
876 void AccessibilityManager::InputMethodChanged(
877 input_method::InputMethodManager* manager,
879 #if defined(USE_ASH) && !defined(USE_ATHENA)
880 // Sticky keys is implemented only in ash.
881 // TODO(dpolukhin): support Athena, crbug.com/408733.
882 ash::Shell::GetInstance()->sticky_keys_controller()->SetModifiersEnabled(
883 manager->IsISOLevel5ShiftUsedByCurrentInputMethod(),
884 manager->IsAltGrUsedByCurrentInputMethod());
886 const chromeos::input_method::InputMethodDescriptor descriptor =
887 manager->GetActiveIMEState()->GetCurrentInputMethod();
888 braille_ime_current_ =
889 (descriptor.id() == extension_misc::kBrailleImeEngineId);
892 void AccessibilityManager::SetProfile(Profile* profile) {
893 pref_change_registrar_.reset();
894 local_state_pref_change_registrar_.reset();
897 // TODO(yoshiki): Move following code to PrefHandler.
898 pref_change_registrar_.reset(new PrefChangeRegistrar);
899 pref_change_registrar_->Init(profile->GetPrefs());
900 pref_change_registrar_->Add(
901 prefs::kAccessibilityLargeCursorEnabled,
902 base::Bind(&AccessibilityManager::UpdateLargeCursorFromPref,
903 base::Unretained(this)));
904 pref_change_registrar_->Add(
905 prefs::kAccessibilityStickyKeysEnabled,
906 base::Bind(&AccessibilityManager::UpdateStickyKeysFromPref,
907 base::Unretained(this)));
908 pref_change_registrar_->Add(
909 prefs::kAccessibilitySpokenFeedbackEnabled,
910 base::Bind(&AccessibilityManager::UpdateSpokenFeedbackFromPref,
911 base::Unretained(this)));
912 pref_change_registrar_->Add(
913 prefs::kAccessibilityHighContrastEnabled,
914 base::Bind(&AccessibilityManager::UpdateHighContrastFromPref,
915 base::Unretained(this)));
916 pref_change_registrar_->Add(
917 prefs::kAccessibilityAutoclickEnabled,
918 base::Bind(&AccessibilityManager::UpdateAutoclickFromPref,
919 base::Unretained(this)));
920 pref_change_registrar_->Add(
921 prefs::kAccessibilityAutoclickDelayMs,
922 base::Bind(&AccessibilityManager::UpdateAutoclickDelayFromPref,
923 base::Unretained(this)));
924 pref_change_registrar_->Add(
925 prefs::kAccessibilityVirtualKeyboardEnabled,
926 base::Bind(&AccessibilityManager::UpdateVirtualKeyboardFromPref,
927 base::Unretained(this)));
929 local_state_pref_change_registrar_.reset(new PrefChangeRegistrar);
930 local_state_pref_change_registrar_->Init(g_browser_process->local_state());
931 local_state_pref_change_registrar_->Add(
932 prefs::kApplicationLocale,
933 base::Bind(&AccessibilityManager::OnLocaleChanged,
934 base::Unretained(this)));
936 content::BrowserAccessibilityState::GetInstance()->AddHistogramCallback(
938 &AccessibilityManager::UpdateChromeOSAccessibilityHistograms,
939 base::Unretained(this)));
942 large_cursor_pref_handler_.HandleProfileChanged(profile_, profile);
943 spoken_feedback_pref_handler_.HandleProfileChanged(profile_, profile);
944 high_contrast_pref_handler_.HandleProfileChanged(profile_, profile);
945 autoclick_pref_handler_.HandleProfileChanged(profile_, profile);
946 autoclick_delay_pref_handler_.HandleProfileChanged(profile_, profile);
947 virtual_keyboard_pref_handler_.HandleProfileChanged(profile_, profile);
949 bool had_profile = (profile_ != NULL);
952 if (!had_profile && profile)
955 UpdateBrailleImeState();
956 UpdateLargeCursorFromPref();
957 UpdateStickyKeysFromPref();
958 UpdateSpokenFeedbackFromPref();
959 UpdateHighContrastFromPref();
960 UpdateAutoclickFromPref();
961 UpdateAutoclickDelayFromPref();
962 UpdateVirtualKeyboardFromPref();
965 #if !defined(USE_ATHENA)
966 void AccessibilityManager::ActiveUserChanged(const std::string& user_id) {
967 SetProfile(ProfileManager::GetActiveUserProfile());
971 void AccessibilityManager::SetProfileForTest(Profile* profile) {
975 void AccessibilityManager::SetBrailleControllerForTest(
976 BrailleController* controller) {
977 g_braille_controller_for_test = controller;
980 void AccessibilityManager::EnableSystemSounds(bool system_sounds_enabled) {
981 system_sounds_enabled_ = system_sounds_enabled;
984 base::TimeDelta AccessibilityManager::PlayShutdownSound() {
985 if (!system_sounds_enabled_)
986 return base::TimeDelta();
987 system_sounds_enabled_ = false;
988 #if !defined(USE_ATHENA)
989 if (!ash::PlaySystemSoundIfSpokenFeedback(SOUND_SHUTDOWN))
990 return base::TimeDelta();
992 return media::SoundsManager::Get()->GetDuration(SOUND_SHUTDOWN);
995 void AccessibilityManager::InjectChromeVox(RenderViewHost* render_view_host) {
996 LoadChromeVoxExtension(profile_, render_view_host, base::Closure());
999 scoped_ptr<AccessibilityStatusSubscription>
1000 AccessibilityManager::RegisterCallback(
1001 const AccessibilityStatusCallback& cb) {
1002 return callback_list_.Add(cb);
1005 void AccessibilityManager::NotifyAccessibilityStatusChanged(
1006 AccessibilityStatusEventDetails& details) {
1007 callback_list_.Notify(details);
1010 void AccessibilityManager::UpdateChromeOSAccessibilityHistograms() {
1011 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosSpokenFeedback",
1012 IsSpokenFeedbackEnabled());
1013 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosHighContrast",
1014 IsHighContrastEnabled());
1015 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosVirtualKeyboard",
1016 IsVirtualKeyboardEnabled());
1017 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosStickyKeys", IsStickyKeysEnabled());
1018 #if !defined(USE_ATHENA)
1019 if (MagnificationManager::Get()) {
1020 uint32 type = MagnificationManager::Get()->IsMagnifierEnabled() ?
1021 MagnificationManager::Get()->GetMagnifierType() : 0;
1022 // '0' means magnifier is disabled.
1023 UMA_HISTOGRAM_ENUMERATION("Accessibility.CrosScreenMagnifier",
1025 ui::kMaxMagnifierType + 1);
1029 const PrefService* const prefs = profile_->GetPrefs();
1030 UMA_HISTOGRAM_BOOLEAN(
1031 "Accessibility.CrosLargeCursor",
1032 prefs->GetBoolean(prefs::kAccessibilityLargeCursorEnabled));
1033 UMA_HISTOGRAM_BOOLEAN(
1034 "Accessibility.CrosAlwaysShowA11yMenu",
1035 prefs->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu));
1037 bool autoclick_enabled =
1038 prefs->GetBoolean(prefs::kAccessibilityAutoclickEnabled);
1039 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosAutoclick", autoclick_enabled);
1040 if (autoclick_enabled) {
1041 // We only want to log the autoclick delay if the user has actually
1042 // enabled autoclick.
1043 UMA_HISTOGRAM_CUSTOM_TIMES(
1044 "Accessibility.CrosAutoclickDelay",
1045 base::TimeDelta::FromMilliseconds(
1046 prefs->GetInteger(prefs::kAccessibilityAutoclickDelayMs)),
1047 base::TimeDelta::FromMilliseconds(1),
1048 base::TimeDelta::FromMilliseconds(3000),
1054 void AccessibilityManager::Observe(
1056 const content::NotificationSource& source,
1057 const content::NotificationDetails& details) {
1059 case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE: {
1060 // Update |profile_| when entering the login screen.
1061 Profile* profile = ProfileManager::GetActiveUserProfile();
1062 if (ProfileHelper::IsSigninProfile(profile))
1063 SetProfile(profile);
1066 case chrome::NOTIFICATION_SESSION_STARTED:
1067 // Update |profile_| when entering a session.
1068 SetProfile(ProfileManager::GetActiveUserProfile());
1070 // Ensure ChromeVox makes announcements at the start of new sessions.
1071 should_speak_chrome_vox_announcements_on_user_screen_ = true;
1073 #if !defined(USE_ATHENA)
1074 // Add a session state observer to be able to monitor session changes.
1075 if (!session_state_observer_.get() && ash::Shell::HasInstance())
1076 session_state_observer_.reset(
1077 new ash::ScopedSessionStateObserver(this));
1080 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
1081 // Update |profile_| when exiting a session or shutting down.
1082 Profile* profile = content::Source<Profile>(source).ptr();
1083 if (profile_ == profile)
1087 case chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED: {
1088 bool is_screen_locked = *content::Details<bool>(details).ptr();
1089 if (spoken_feedback_enabled_) {
1090 if (is_screen_locked)
1091 LoadChromeVoxToLockScreen(base::Closure());
1092 // If spoken feedback was enabled, make sure it is also enabled on
1094 // The status tray gets verbalized by user screen ChromeVox, so we need
1095 // to load it on the user screen even if the screen is locked.
1096 LoadChromeVoxToUserScreen(base::Closure());
1103 void AccessibilityManager::OnBrailleDisplayStateChanged(
1104 const DisplayState& display_state) {
1105 braille_display_connected_ = display_state.available;
1106 if (braille_display_connected_) {
1107 EnableSpokenFeedback(true, ui::A11Y_NOTIFICATION_SHOW);
1109 UpdateBrailleImeState();
1111 AccessibilityStatusEventDetails details(
1112 ACCESSIBILITY_BRAILLE_DISPLAY_CONNECTION_STATE_CHANGED,
1113 braille_display_connected_,
1114 ui::A11Y_NOTIFICATION_SHOW);
1115 NotifyAccessibilityStatusChanged(details);
1118 void AccessibilityManager::OnBrailleKeyEvent(const KeyEvent& event) {
1119 // Ensure the braille IME is active on braille keyboard (dots) input.
1120 if ((event.command ==
1121 extensions::api::braille_display_private::KEY_COMMAND_DOTS) &&
1122 !braille_ime_current_) {
1123 input_method::InputMethodManager::Get()
1124 ->GetActiveIMEState()
1125 ->ChangeInputMethod(extension_misc::kBrailleImeEngineId,
1126 false /* show_message */);
1130 void AccessibilityManager::PostLoadChromeVox(Profile* profile) {
1131 #if !defined(USE_ATHENA)
1132 // Do any setup work needed immediately after ChromeVox actually loads.
1133 if (system_sounds_enabled_)
1134 ash::PlaySystemSoundAlways(SOUND_SPOKEN_FEEDBACK_ENABLED);
1137 ExtensionAccessibilityEventRouter::GetInstance()->
1138 OnChromeVoxLoadStateChanged(profile_,
1139 IsSpokenFeedbackEnabled(),
1140 chrome_vox_loaded_on_lock_screen_ ||
1141 should_speak_chrome_vox_announcements_on_user_screen_);
1143 should_speak_chrome_vox_announcements_on_user_screen_ =
1144 chrome_vox_loaded_on_lock_screen_;
1147 void AccessibilityManager::PostUnloadChromeVox(Profile* profile) {
1148 #if !defined(USE_ATHENA)
1149 // Do any teardown work needed immediately after ChromeVox actually unloads.
1150 if (system_sounds_enabled_)
1151 ash::PlaySystemSoundAlways(SOUND_SPOKEN_FEEDBACK_DISABLED);
1153 // Clear the accessibility focus ring.
1154 AccessibilityFocusRingController::GetInstance()->SetFocusRing(
1155 std::vector<gfx::Rect>());
1158 } // namespace chromeos