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