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