Upstream version 10.39.225.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/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"
68
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;
74
75 namespace chromeos {
76
77 namespace {
78
79 static chromeos::AccessibilityManager* g_accessibility_manager = NULL;
80
81 static BrailleController* g_braille_controller_for_test = NULL;
82
83 BrailleController* GetBrailleController() {
84   return g_braille_controller_for_test
85       ? g_braille_controller_for_test
86       : BrailleController::GetInstance();
87 }
88
89 base::FilePath GetChromeVoxPath() {
90   base::FilePath path;
91   if (!PathService::Get(chrome::DIR_RESOURCES, &path))
92     NOTREACHED();
93   path = path.Append(extension_misc::kChromeVoxExtensionPath);
94   return path;
95 }
96
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 {
100  public:
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,
105                       int render_view_id)
106       : extension_id_(extension_id),
107         render_process_id_(render_process_id),
108         render_view_id_(render_view_id) {}
109
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);
114   }
115
116   // Finally, call this method once to fetch all of the resources and
117   // load them. This method will delete this object when done.
118   void Run() {
119     if (resources_.empty()) {
120       delete this;
121       return;
122     }
123
124     extensions::ExtensionResource resource = resources_.front();
125     resources_.pop();
126     scoped_refptr<FileReader> reader(new FileReader(resource, base::Bind(
127         &ContentScriptLoader::OnFileLoaded, base::Unretained(this))));
128     reader->Start();
129   }
130
131  private:
132   void OnFileLoaded(bool success, const std::string& data) {
133     if (success) {
134       ExtensionMsg_ExecuteCode_Params params;
135       params.request_id = 0;
136       params.extension_id = extension_id_;
137       params.is_javascript = true;
138       params.code = data;
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;
143
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));
149       }
150     }
151     Run();
152   }
153
154   std::string extension_id_;
155   int render_process_id_;
156   int render_view_id_;
157   std::queue<extensions::ExtensionResource> resources_;
158 };
159
160 void InjectChromeVoxContentScript(
161     ExtensionService* extension_service,
162     int render_process_id,
163     int render_view_id,
164     const base::Closure& done_cb);
165
166 void LoadChromeVoxExtension(
167     Profile* profile,
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,
176         extension_service,
177         render_view_host->GetProcess()->GetID(),
178         render_view_host->GetRoutingID(),
179         done_cb);
180   }
181   extension_service->component_loader()->AddChromeVoxExtension(done_cb);
182 }
183
184 void InjectChromeVoxContentScript(
185     ExtensionService* extension_service,
186     int render_process_id,
187     int render_view_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)
195     return;
196   const extensions::Extension* extension =
197       extension_service->extensions()->GetByID(
198           extension_misc::kChromeVoxExtensionId);
199
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));
213
214   // Inject ChromeVox' content scripts.
215   ContentScriptLoader* loader = new ContentScriptLoader(
216       extension->id(), render_view_host->GetProcess()->GetID(),
217       render_view_host->GetRoutingID());
218
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);
228     }
229   }
230   loader->Run();  // It cleans itself up when done.
231 }
232
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);
238 }
239
240 }  // namespace
241
242 ///////////////////////////////////////////////////////////////////////////////
243 // AccessibilityStatusEventDetails
244
245 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
246     AccessibilityNotificationType notification_type,
247     bool enabled,
248     ash::AccessibilityNotificationVisibility notify)
249   : notification_type(notification_type),
250     enabled(enabled),
251     magnifier_type(ash::kDefaultMagnifierType),
252     notify(notify) {}
253
254 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
255     AccessibilityNotificationType notification_type,
256     bool enabled,
257     ash::MagnifierType magnifier_type,
258     ash::AccessibilityNotificationVisibility notify)
259   : notification_type(notification_type),
260     enabled(enabled),
261     magnifier_type(magnifier_type),
262     notify(notify) {}
263
264 ///////////////////////////////////////////////////////////////////////////////
265 //
266 // AccessibilityManager::PrefHandler
267
268 AccessibilityManager::PrefHandler::PrefHandler(const char* pref_path)
269     : pref_path_(pref_path) {}
270
271 AccessibilityManager::PrefHandler::~PrefHandler() {}
272
273 void AccessibilityManager::PrefHandler::HandleProfileChanged(
274     Profile* previous_profile, Profile* current_profile) {
275   // Returns if the current profile is null.
276   if (!current_profile)
277     return;
278
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())
294       return;
295
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);
300   }
301 }
302
303 ///////////////////////////////////////////////////////////////////////////////
304 //
305 // AccessibilityManager
306
307 // static
308 void AccessibilityManager::Initialize() {
309   CHECK(g_accessibility_manager == NULL);
310   g_accessibility_manager = new AccessibilityManager();
311 }
312
313 // static
314 void AccessibilityManager::Shutdown() {
315   CHECK(g_accessibility_manager);
316   delete g_accessibility_manager;
317   g_accessibility_manager = NULL;
318 }
319
320 // static
321 AccessibilityManager* AccessibilityManager::Get() {
322   return g_accessibility_manager;
323 }
324
325 AccessibilityManager::AccessibilityManager()
326     : profile_(NULL),
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());
362
363   input_method::InputMethodManager::Get()->AddObserver(this);
364
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));
369   manager->Initialize(
370       SOUND_SPOKEN_FEEDBACK_ENABLED,
371       bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_ENABLED_WAV));
372   manager->Initialize(
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));
381 }
382
383 AccessibilityManager::~AccessibilityManager() {
384   CHECK(this == g_accessibility_manager);
385   AccessibilityStatusEventDetails details(
386       ACCESSIBILITY_MANAGER_SHUTDOWN,
387       false,
388       ash::A11Y_NOTIFICATION_NONE);
389   NotifyAccessibilityStatusChanged(details);
390   input_method::InputMethodManager::Get()->RemoveObserver(this);
391 }
392
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();
400        ++it) {
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))
410       return true;
411   }
412   return false;
413 }
414
415 bool AccessibilityManager::ShouldEnableCursorCompositing() {
416 #if defined(OS_CHROMEOS)
417   if (!profile_)
418     return false;
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))
425     return true;
426 #endif
427   return false;
428 }
429
430 void AccessibilityManager::EnableLargeCursor(bool enabled) {
431   if (!profile_)
432     return;
433
434   PrefService* pref_service = profile_->GetPrefs();
435   pref_service->SetBoolean(prefs::kAccessibilityLargeCursorEnabled, enabled);
436   pref_service->CommitPendingWrite();
437 }
438
439 void AccessibilityManager::UpdateLargeCursorFromPref() {
440   if (!profile_)
441     return;
442
443   const bool enabled =
444       profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityLargeCursorEnabled);
445
446   if (large_cursor_enabled_ == enabled)
447     return;
448
449   large_cursor_enabled_ = enabled;
450
451   AccessibilityStatusEventDetails details(
452       ACCESSIBILITY_TOGGLE_LARGE_CURSOR,
453       enabled,
454       ash::A11Y_NOTIFICATION_NONE);
455
456   NotifyAccessibilityStatusChanged(details);
457 #if !defined(USE_ATHENA)
458   // crbug.com/408733 (and for all USE_ATHENA in this file)
459
460 #if defined(USE_ASH)
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);
464 #endif
465
466 #if defined(OS_CHROMEOS)
467   ash::Shell::GetInstance()->SetCursorCompositingEnabled(
468       ShouldEnableCursorCompositing());
469 #endif
470
471 #endif // !USE_ATHENA
472 }
473
474 bool AccessibilityManager::IsIncognitoAllowed() {
475   // Supervised users can't create incognito-mode windows.
476   return !(user_manager::UserManager::Get()->IsLoggedInAsSupervisedUser());
477 }
478
479 bool AccessibilityManager::IsLargeCursorEnabled() {
480   return large_cursor_enabled_;
481 }
482
483 void AccessibilityManager::EnableStickyKeys(bool enabled) {
484   if (!profile_)
485     return;
486   PrefService* pref_service = profile_->GetPrefs();
487   pref_service->SetBoolean(prefs::kAccessibilityStickyKeysEnabled, enabled);
488   pref_service->CommitPendingWrite();
489 }
490
491 bool AccessibilityManager::IsStickyKeysEnabled() {
492   return sticky_keys_enabled_;
493 }
494
495 void AccessibilityManager::UpdateStickyKeysFromPref() {
496   if (!profile_)
497     return;
498
499   const bool enabled =
500       profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityStickyKeysEnabled);
501
502   if (sticky_keys_enabled_ == enabled)
503     return;
504
505   sticky_keys_enabled_ = enabled;
506 #if defined(USE_ASH) && !defined(USE_ATHENA)
507   ash::Shell::GetInstance()->sticky_keys_controller()->Enable(enabled);
508 #endif
509 }
510
511 void AccessibilityManager::EnableSpokenFeedback(
512     bool enabled,
513     ash::AccessibilityNotificationVisibility notify) {
514   if (!profile_)
515     return;
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);
520 #endif
521
522   spoken_feedback_notification_ = notify;
523
524   PrefService* pref_service = profile_->GetPrefs();
525   pref_service->SetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled, enabled);
526   pref_service->CommitPendingWrite();
527
528   spoken_feedback_notification_ = ash::A11Y_NOTIFICATION_NONE;
529 }
530
531 void AccessibilityManager::UpdateSpokenFeedbackFromPref() {
532   if (!profile_)
533     return;
534
535   const bool enabled = profile_->GetPrefs()->GetBoolean(
536       prefs::kAccessibilitySpokenFeedbackEnabled);
537
538   if (spoken_feedback_enabled_ == enabled)
539     return;
540
541   spoken_feedback_enabled_ = enabled;
542
543   ExtensionAccessibilityEventRouter::GetInstance()->
544       SetAccessibilityEnabled(enabled);
545
546   AccessibilityStatusEventDetails details(
547       ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK,
548       enabled,
549       spoken_feedback_notification_);
550
551   NotifyAccessibilityStatusChanged(details);
552
553   if (enabled) {
554     LoadChromeVox();
555   } else {
556     UnloadChromeVox();
557   }
558   UpdateBrailleImeState();
559 }
560
561 void AccessibilityManager::LoadChromeVox() {
562   base::Closure done_cb = base::Bind(&AccessibilityManager::PostLoadChromeVox,
563                                      weak_ptr_factory_.GetWeakPtr(),
564                                      profile_);
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);
571   } else {
572     LoadChromeVoxToUserScreen(done_cb);
573   }
574 }
575
576 void AccessibilityManager::LoadChromeVoxToUserScreen(
577     const base::Closure& done_cb) {
578   if (chrome_vox_loaded_on_user_screen_)
579     return;
580
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;
584
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();
592     }
593
594     // Lock screen uses the signin progile.
595     chrome_vox_loaded_on_lock_screen_ = true;
596   }
597
598   chrome_vox_loaded_on_user_screen_ = true;
599   LoadChromeVoxExtension(
600       profile_, login_web_ui ?
601       login_web_ui->GetWebContents()->GetRenderViewHost() : NULL,
602       done_cb);
603 }
604
605 void AccessibilityManager::LoadChromeVoxToLockScreen(
606     const base::Closure& done_cb) {
607   if (chrome_vox_loaded_on_lock_screen_)
608     return;
609
610   ScreenLocker* screen_locker = ScreenLocker::default_screen_locker();
611   if (screen_locker && screen_locker->locked()) {
612     content::WebUI* lock_web_ui = screen_locker->GetAssociatedWebUI();
613     if (lock_web_ui) {
614       Profile* profile = Profile::FromWebUI(lock_web_ui);
615       chrome_vox_loaded_on_lock_screen_ = true;
616       LoadChromeVoxExtension(
617           profile,
618           lock_web_ui->GetWebContents()->GetRenderViewHost(),
619           done_cb);
620     }
621   }
622 }
623
624 void AccessibilityManager::UnloadChromeVox() {
625   if (chrome_vox_loaded_on_lock_screen_)
626     UnloadChromeVoxFromLockScreen();
627
628   if (chrome_vox_loaded_on_user_screen_) {
629     UnloadChromeVoxExtension(profile_);
630     chrome_vox_loaded_on_user_screen_ = false;
631   }
632
633   PostUnloadChromeVox(profile_);
634 }
635
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;
641 }
642
643 bool AccessibilityManager::IsSpokenFeedbackEnabled() {
644   return spoken_feedback_enabled_;
645 }
646
647 void AccessibilityManager::ToggleSpokenFeedback(
648     ash::AccessibilityNotificationVisibility notify) {
649   EnableSpokenFeedback(!IsSpokenFeedbackEnabled(), notify);
650 }
651
652 void AccessibilityManager::EnableHighContrast(bool enabled) {
653   if (!profile_)
654     return;
655
656   PrefService* pref_service = profile_->GetPrefs();
657   pref_service->SetBoolean(prefs::kAccessibilityHighContrastEnabled, enabled);
658   pref_service->CommitPendingWrite();
659 }
660
661 void AccessibilityManager::UpdateHighContrastFromPref() {
662   if (!profile_)
663     return;
664
665   const bool enabled = profile_->GetPrefs()->GetBoolean(
666       prefs::kAccessibilityHighContrastEnabled);
667
668   if (high_contrast_enabled_ == enabled)
669     return;
670
671   high_contrast_enabled_ = enabled;
672
673   AccessibilityStatusEventDetails details(
674       ACCESSIBILITY_TOGGLE_HIGH_CONTRAST_MODE,
675       enabled,
676       ash::A11Y_NOTIFICATION_NONE);
677
678   NotifyAccessibilityStatusChanged(details);
679
680 #if !defined(USE_ATHENA)
681
682 #if defined(USE_ASH)
683   ash::Shell::GetInstance()->high_contrast_controller()->SetEnabled(enabled);
684 #endif
685
686 #if defined(OS_CHROMEOS)
687   ash::Shell::GetInstance()->SetCursorCompositingEnabled(
688       ShouldEnableCursorCompositing());
689 #endif
690
691 #endif
692 }
693
694 void AccessibilityManager::OnLocaleChanged() {
695   if (!profile_)
696     return;
697
698   if (!IsSpokenFeedbackEnabled())
699     return;
700
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);
706 }
707
708 void AccessibilityManager::PlayEarcon(int sound_key) {
709   DCHECK(sound_key < chromeos::SOUND_COUNT);
710   ash::PlaySystemSoundIfSpokenFeedback(sound_key);
711 }
712
713 bool AccessibilityManager::IsHighContrastEnabled() {
714   return high_contrast_enabled_;
715 }
716
717 void AccessibilityManager::EnableAutoclick(bool enabled) {
718   if (!profile_)
719     return;
720
721   PrefService* pref_service = profile_->GetPrefs();
722   pref_service->SetBoolean(prefs::kAccessibilityAutoclickEnabled, enabled);
723   pref_service->CommitPendingWrite();
724 }
725
726 bool AccessibilityManager::IsAutoclickEnabled() {
727   return autoclick_enabled_;
728 }
729
730 void AccessibilityManager::UpdateAutoclickFromPref() {
731   bool enabled =
732       profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityAutoclickEnabled);
733
734   if (autoclick_enabled_ == enabled)
735     return;
736   autoclick_enabled_ = enabled;
737
738 #if defined(USE_ASH) && !defined(USE_ATHENA)
739   ash::Shell::GetInstance()->autoclick_controller()->SetEnabled(enabled);
740 #endif
741 }
742
743 void AccessibilityManager::SetAutoclickDelay(int delay_ms) {
744   if (!profile_)
745     return;
746
747   PrefService* pref_service = profile_->GetPrefs();
748   pref_service->SetInteger(prefs::kAccessibilityAutoclickDelayMs, delay_ms);
749   pref_service->CommitPendingWrite();
750 }
751
752 int AccessibilityManager::GetAutoclickDelay() const {
753   return autoclick_delay_ms_;
754 }
755
756 void AccessibilityManager::UpdateAutoclickDelayFromPref() {
757   int autoclick_delay_ms =
758       profile_->GetPrefs()->GetInteger(prefs::kAccessibilityAutoclickDelayMs);
759
760   if (autoclick_delay_ms == autoclick_delay_ms_)
761     return;
762   autoclick_delay_ms_ = autoclick_delay_ms;
763
764 #if defined(USE_ASH) && !defined(USE_ATHENA)
765   ash::Shell::GetInstance()->autoclick_controller()->SetAutoclickDelay(
766       autoclick_delay_ms_);
767 #endif
768 }
769
770 void AccessibilityManager::EnableVirtualKeyboard(bool enabled) {
771   if (!profile_)
772     return;
773
774   PrefService* pref_service = profile_->GetPrefs();
775   pref_service->SetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled,
776                            enabled);
777   pref_service->CommitPendingWrite();
778 }
779
780 bool AccessibilityManager::IsVirtualKeyboardEnabled() {
781   return virtual_keyboard_enabled_;
782 }
783
784 void AccessibilityManager::UpdateVirtualKeyboardFromPref() {
785   if (!profile_)
786     return;
787
788   const bool enabled = profile_->GetPrefs()->GetBoolean(
789       prefs::kAccessibilityVirtualKeyboardEnabled);
790
791   if (virtual_keyboard_enabled_ == enabled)
792     return;
793   virtual_keyboard_enabled_ = enabled;
794
795   AccessibilityStatusEventDetails details(
796       ACCESSIBILITY_TOGGLE_VIRTUAL_KEYBOARD,
797       enabled,
798       ash::A11Y_NOTIFICATION_NONE);
799
800   NotifyAccessibilityStatusChanged(details);
801
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
810   // change.
811   if (keyboard::IsKeyboardEnabled())
812     ash::Shell::GetInstance()->CreateKeyboard();
813   else
814     ash::Shell::GetInstance()->DeactivateKeyboard();
815 #endif
816 }
817
818 bool AccessibilityManager::IsBrailleDisplayConnected() const {
819   return braille_display_connected_;
820 }
821
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(
827       BrowserThread::IO,
828       FROM_HERE,
829       base::Bind(&BrailleController::GetDisplayState,
830                  base::Unretained(braille_controller)),
831       base::Bind(&AccessibilityManager::ReceiveBrailleDisplayState,
832                  weak_ptr_factory_.GetWeakPtr()));
833 }
834
835 void AccessibilityManager::ReceiveBrailleDisplayState(
836     scoped_ptr<extensions::api::braille_display_private::DisplayState> state) {
837   OnBrailleDisplayStateChanged(*state);
838 }
839
840 void AccessibilityManager::UpdateBrailleImeState() {
841   if (!profile_)
842     return;
843   PrefService* pref_service = profile_->GetPrefs();
844   std::vector<std::string> preload_engines;
845   base::SplitString(pref_service->GetString(prefs::kLanguagePreloadEngines),
846                     ',',
847                     &preload_engines);
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)
856     return;
857   if (should_be_enabled)
858     preload_engines.push_back(extension_misc::kBrailleImeEngineId);
859   else
860     preload_engines.erase(it);
861   pref_service->SetString(prefs::kLanguagePreloadEngines,
862                           JoinString(preload_engines, ','));
863   braille_ime_current_ = false;
864 }
865
866 // Overridden from InputMethodManager::Observer.
867 void AccessibilityManager::InputMethodChanged(
868     input_method::InputMethodManager* manager,
869     bool show_message) {
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());
876 #endif
877   const chromeos::input_method::InputMethodDescriptor descriptor =
878       manager->GetActiveIMEState()->GetCurrentInputMethod();
879   braille_ime_current_ =
880       (descriptor.id() == extension_misc::kBrailleImeEngineId);
881 }
882
883 void AccessibilityManager::SetProfile(Profile* profile) {
884   pref_change_registrar_.reset();
885   local_state_pref_change_registrar_.reset();
886
887   if (profile) {
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)));
919
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)));
926
927     content::BrowserAccessibilityState::GetInstance()->AddHistogramCallback(
928         base::Bind(
929             &AccessibilityManager::UpdateChromeOSAccessibilityHistograms,
930             base::Unretained(this)));
931   }
932
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);
939
940   bool had_profile = (profile_ != NULL);
941   profile_ = profile;
942
943   if (!had_profile && profile)
944     CheckBrailleState();
945   else
946     UpdateBrailleImeState();
947   UpdateLargeCursorFromPref();
948   UpdateStickyKeysFromPref();
949   UpdateSpokenFeedbackFromPref();
950   UpdateHighContrastFromPref();
951   UpdateAutoclickFromPref();
952   UpdateAutoclickDelayFromPref();
953   UpdateVirtualKeyboardFromPref();
954 }
955
956 void AccessibilityManager::ActiveUserChanged(const std::string& user_id) {
957   SetProfile(ProfileManager::GetActiveUserProfile());
958 }
959
960 void AccessibilityManager::SetProfileForTest(Profile* profile) {
961   SetProfile(profile);
962 }
963
964 void AccessibilityManager::SetBrailleControllerForTest(
965     BrailleController* controller) {
966   g_braille_controller_for_test = controller;
967 }
968
969 void AccessibilityManager::EnableSystemSounds(bool system_sounds_enabled) {
970   system_sounds_enabled_ = system_sounds_enabled;
971 }
972
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();
980 #endif
981   return media::SoundsManager::Get()->GetDuration(SOUND_SHUTDOWN);
982 }
983
984 void AccessibilityManager::InjectChromeVox(RenderViewHost* render_view_host) {
985   LoadChromeVoxExtension(profile_, render_view_host, base::Closure());
986 }
987
988 scoped_ptr<AccessibilityStatusSubscription>
989     AccessibilityManager::RegisterCallback(
990         const AccessibilityStatusCallback& cb) {
991   return callback_list_.Add(cb);
992 }
993
994 void AccessibilityManager::NotifyAccessibilityStatusChanged(
995     AccessibilityStatusEventDetails& details) {
996   callback_list_.Notify(details);
997 }
998
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",
1012                               type,
1013                               ash::kMaxMagnifierType + 1);
1014   }
1015   if (profile_) {
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));
1023
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),
1036           50);
1037     }
1038   }
1039 }
1040
1041 void AccessibilityManager::Observe(
1042     int type,
1043     const content::NotificationSource& source,
1044     const content::NotificationDetails& details) {
1045   switch (type) {
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);
1051       break;
1052     }
1053     case chrome::NOTIFICATION_SESSION_STARTED:
1054       // Update |profile_| when entering a session.
1055       SetProfile(ProfileManager::GetActiveUserProfile());
1056
1057       // Ensure ChromeVox makes announcements at the start of new sessions.
1058       should_speak_chrome_vox_announcements_on_user_screen_ = true;
1059
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));
1064       break;
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)
1069         SetProfile(NULL);
1070       break;
1071     }
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
1078         // the user screen.
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());
1082       }
1083       break;
1084     }
1085   }
1086 }
1087
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);
1093   }
1094   UpdateBrailleImeState();
1095
1096   AccessibilityStatusEventDetails details(
1097       ACCESSIBILITY_BRAILLE_DISPLAY_CONNECTION_STATE_CHANGED,
1098       braille_display_connected_,
1099       ash::A11Y_NOTIFICATION_SHOW);
1100   NotifyAccessibilityStatusChanged(details);
1101 }
1102
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 */);
1112   }
1113 }
1114
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);
1119
1120   ExtensionAccessibilityEventRouter::GetInstance()->
1121       OnChromeVoxLoadStateChanged(profile_,
1122           IsSpokenFeedbackEnabled(),
1123           chrome_vox_loaded_on_lock_screen_ ||
1124               should_speak_chrome_vox_announcements_on_user_screen_);
1125
1126   should_speak_chrome_vox_announcements_on_user_screen_ =
1127       chrome_vox_loaded_on_lock_screen_;
1128 }
1129
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);
1134 }
1135
1136 }  // namespace chromeos