accessibility_manager.cc revision a02191e04bc25c4935f804f2c080ae28663d096d
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/accessibility_private.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
65using content::BrowserThread;
66using content::RenderViewHost;
67using extensions::api::braille_display_private::BrailleController;
68using extensions::api::braille_display_private::DisplayState;
69
70namespace chromeos {
71
72namespace {
73
74static chromeos::AccessibilityManager* g_accessibility_manager = NULL;
75
76static BrailleController* g_braille_controller_for_test = NULL;
77
78BrailleController* GetBrailleController() {
79  return g_braille_controller_for_test
80      ? g_braille_controller_for_test
81      : BrailleController::GetInstance();
82}
83
84base::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.
94class 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
154void 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
199void 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
211AccessibilityStatusEventDetails::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
220AccessibilityStatusEventDetails::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
234AccessibilityManager::PrefHandler::PrefHandler(const char* pref_path)
235    : pref_path_(pref_path) {}
236
237AccessibilityManager::PrefHandler::~PrefHandler() {}
238
239void 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
274void AccessibilityManager::Initialize() {
275  CHECK(g_accessibility_manager == NULL);
276  g_accessibility_manager = new AccessibilityManager();
277}
278
279// static
280void AccessibilityManager::Shutdown() {
281  CHECK(g_accessibility_manager);
282  delete g_accessibility_manager;
283  g_accessibility_manager = NULL;
284}
285
286// static
287AccessibilityManager* AccessibilityManager::Get() {
288  return g_accessibility_manager;
289}
290
291AccessibilityManager::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
341AccessibilityManager::~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
351bool 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
373bool 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
379void 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
388void 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
419bool AccessibilityManager::IsIncognitoAllowed() {
420  UserManager* user_manager = UserManager::Get();
421  // Supervised users can't create incognito-mode windows.
422  return !(user_manager->IsLoggedInAsLocallyManagedUser());
423}
424
425bool AccessibilityManager::IsLargeCursorEnabled() {
426  return large_cursor_enabled_;
427}
428
429void 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
437bool AccessibilityManager::IsStickyKeysEnabled() {
438  return sticky_keys_enabled_;
439}
440
441void 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
458void 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
478void 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
507void 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
520void 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
543void 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
559void 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
571void 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
578bool AccessibilityManager::IsSpokenFeedbackEnabled() {
579  return spoken_feedback_enabled_;
580}
581
582void AccessibilityManager::ToggleSpokenFeedback(
583    ash::AccessibilityNotificationVisibility notify) {
584  EnableSpokenFeedback(!IsSpokenFeedbackEnabled(), notify);
585}
586
587void 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
596void 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
625void 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
639bool AccessibilityManager::IsHighContrastEnabled() {
640  return high_contrast_enabled_;
641}
642
643void 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
652bool AccessibilityManager::IsAutoclickEnabled() {
653  return autoclick_enabled_;
654}
655
656void 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
669void 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
678int AccessibilityManager::GetAutoclickDelay() const {
679  return autoclick_delay_ms_;
680}
681
682void 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
696void 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
705bool AccessibilityManager::IsVirtualKeyboardEnabled() {
706  return virtual_keyboard_enabled_;
707}
708
709void 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
736bool AccessibilityManager::IsBrailleDisplayConnected() const {
737  return braille_display_connected_;
738}
739
740void 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
753void AccessibilityManager::ReceiveBrailleDisplayState(
754    scoped_ptr<extensions::api::braille_display_private::DisplayState> state) {
755  OnDisplayStateChanged(*state);
756}
757
758// Overridden from InputMethodManager::Observer.
759void 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
770void 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
842void AccessibilityManager::ActiveUserChanged(const std::string& user_id) {
843  SetProfile(ProfileManager::GetActiveUserProfile());
844}
845
846void AccessibilityManager::SetProfileForTest(Profile* profile) {
847  SetProfile(profile);
848}
849
850void AccessibilityManager::SetBrailleControllerForTest(
851    BrailleController* controller) {
852  g_braille_controller_for_test = controller;
853}
854
855void AccessibilityManager::EnableSystemSounds(bool system_sounds_enabled) {
856  system_sounds_enabled_ = system_sounds_enabled;
857}
858
859base::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
868void AccessibilityManager::InjectChromeVox(RenderViewHost* render_view_host) {
869  LoadChromeVoxExtension(profile_, render_view_host);
870}
871
872scoped_ptr<AccessibilityStatusSubscription>
873    AccessibilityManager::RegisterCallback(
874        const AccessibilityStatusCallback& cb) {
875  return callback_list_.Add(cb);
876}
877
878void AccessibilityManager::NotifyAccessibilityStatusChanged(
879    AccessibilityStatusEventDetails& details) {
880  callback_list_.Notify(details);
881}
882
883void 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
923void 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
978void 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
991void 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
1006void 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
1013