Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / chromeos / accessibility / accessibility_manager.cc
blob4b6e862f8a7c796907514b95697c745f53459f15
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
7 #include "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 "ash/wm/event_rewriter_event_filter.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/memory/singleton.h"
18 #include "base/metrics/histogram.h"
19 #include "base/path_service.h"
20 #include "base/prefs/pref_member.h"
21 #include "base/prefs/pref_service.h"
22 #include "base/time/time.h"
23 #include "base/values.h"
24 #include "chrome/browser/accessibility/accessibility_extension_api.h"
25 #include "chrome/browser/browser_process.h"
26 #include "chrome/browser/chrome_notification_types.h"
27 #include "chrome/browser/chromeos/accessibility/magnification_manager.h"
28 #include "chrome/browser/chromeos/login/login_display_host.h"
29 #include "chrome/browser/chromeos/login/login_display_host_impl.h"
30 #include "chrome/browser/chromeos/login/screen_locker.h"
31 #include "chrome/browser/chromeos/login/user_manager.h"
32 #include "chrome/browser/chromeos/login/webui_login_view.h"
33 #include "chrome/browser/chromeos/profiles/profile_helper.h"
34 #include "chrome/browser/extensions/component_loader.h"
35 #include "chrome/browser/extensions/extension_service.h"
36 #include "chrome/browser/extensions/extension_system.h"
37 #include "chrome/browser/profiles/profile.h"
38 #include "chrome/browser/profiles/profile_manager.h"
39 #include "chrome/browser/speech/tts_controller.h"
40 #include "chrome/common/chrome_paths.h"
41 #include "chrome/common/extensions/api/experimental_accessibility.h"
42 #include "chrome/common/extensions/extension_messages.h"
43 #include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h"
44 #include "chrome/common/pref_names.h"
45 #include "chromeos/audio/chromeos_sounds.h"
46 #include "chromeos/login/login_state.h"
47 #include "content/public/browser/browser_accessibility_state.h"
48 #include "content/public/browser/browser_thread.h"
49 #include "content/public/browser/notification_details.h"
50 #include "content/public/browser/notification_service.h"
51 #include "content/public/browser/notification_source.h"
52 #include "content/public/browser/render_process_host.h"
53 #include "content/public/browser/render_view_host.h"
54 #include "content/public/browser/web_contents.h"
55 #include "content/public/browser/web_ui.h"
56 #include "extensions/browser/file_reader.h"
57 #include "extensions/common/extension.h"
58 #include "extensions/common/extension_resource.h"
59 #include "grit/browser_resources.h"
60 #include "grit/generated_resources.h"
61 #include "media/audio/sounds/sounds_manager.h"
62 #include "ui/base/l10n/l10n_util.h"
63 #include "ui/base/resource/resource_bundle.h"
65 using content::BrowserThread;
66 using content::RenderViewHost;
67 using extensions::api::braille_display_private::BrailleController;
68 using extensions::api::braille_display_private::DisplayState;
70 namespace chromeos {
72 namespace {
74 static chromeos::AccessibilityManager* g_accessibility_manager = NULL;
76 static BrailleController* g_braille_controller_for_test = NULL;
78 BrailleController* GetBrailleController() {
79 return g_braille_controller_for_test
80 ? g_braille_controller_for_test
81 : BrailleController::GetInstance();
84 base::FilePath GetChromeVoxPath() {
85 base::FilePath path;
86 if (!PathService::Get(chrome::DIR_RESOURCES, &path))
87 NOTREACHED();
88 path = path.Append(extension_misc::kChromeVoxExtensionPath);
89 return path;
92 // Helper class that directly loads an extension's content scripts into
93 // all of the frames corresponding to a given RenderViewHost.
94 class ContentScriptLoader {
95 public:
96 // Initialize the ContentScriptLoader with the ID of the extension
97 // and the RenderViewHost where the scripts should be loaded.
98 ContentScriptLoader(const std::string& extension_id,
99 int render_process_id,
100 int render_view_id)
101 : extension_id_(extension_id),
102 render_process_id_(render_process_id),
103 render_view_id_(render_view_id) {}
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);
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;
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();
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;
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));
145 Run();
148 std::string extension_id_;
149 int render_process_id_;
150 int render_view_id_;
151 std::queue<extensions::ExtensionResource> resources_;
154 void LoadChromeVoxExtension(Profile* profile, content::WebUI* login_web_ui) {
155 ExtensionService* extension_service =
156 extensions::ExtensionSystem::Get(profile)->extension_service();
157 base::FilePath path = GetChromeVoxPath();
158 std::string extension_id =
159 extension_service->component_loader()->Add(IDR_CHROMEVOX_MANIFEST,
160 path);
161 if (login_web_ui) {
162 ExtensionService* extension_service =
163 extensions::ExtensionSystem::Get(profile)->extension_service();
164 const extensions::Extension* extension =
165 extension_service->extensions()->GetByID(extension_id);
167 RenderViewHost* render_view_host =
168 login_web_ui->GetWebContents()->GetRenderViewHost();
169 // Set a flag to tell ChromeVox that it's just been enabled,
170 // so that it won't interrupt our speech feedback enabled message.
171 ExtensionMsg_ExecuteCode_Params params;
172 params.request_id = 0;
173 params.extension_id = extension->id();
174 params.is_javascript = true;
175 params.code = "window.INJECTED_AFTER_LOAD = true;";
176 params.run_at = extensions::UserScript::DOCUMENT_IDLE;
177 params.all_frames = true;
178 params.in_main_world = false;
179 render_view_host->Send(new ExtensionMsg_ExecuteCode(
180 render_view_host->GetRoutingID(), params));
182 // Inject ChromeVox' content scripts.
183 ContentScriptLoader* loader = new ContentScriptLoader(
184 extension->id(), render_view_host->GetProcess()->GetID(),
185 render_view_host->GetRoutingID());
187 const extensions::UserScriptList& content_scripts =
188 extensions::ContentScriptsInfo::GetContentScripts(extension);
189 for (size_t i = 0; i < content_scripts.size(); i++) {
190 const extensions::UserScript& script = content_scripts[i];
191 for (size_t j = 0; j < script.js_scripts().size(); ++j) {
192 const extensions::UserScript::File &file = script.js_scripts()[j];
193 extensions::ExtensionResource resource = extension->GetResource(
194 file.relative_path());
195 loader->AppendScript(resource);
198 loader->Run(); // It cleans itself up when done.
202 void UnloadChromeVoxExtension(Profile* profile) {
203 base::FilePath path = GetChromeVoxPath();
204 ExtensionService* extension_service =
205 extensions::ExtensionSystem::Get(profile)->extension_service();
206 extension_service->component_loader()->Remove(path);
209 } // namespace
211 ///////////////////////////////////////////////////////////////////////////////
212 // AccessibilityStatusEventDetails
214 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
215 bool enabled,
216 ash::AccessibilityNotificationVisibility notify)
217 : enabled(enabled),
218 magnifier_type(ash::kDefaultMagnifierType),
219 notify(notify) {}
221 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
222 bool enabled,
223 ash::MagnifierType magnifier_type,
224 ash::AccessibilityNotificationVisibility notify)
225 : enabled(enabled),
226 magnifier_type(magnifier_type),
227 notify(notify) {}
229 ///////////////////////////////////////////////////////////////////////////////
231 // AccessibilityManager::PrefHandler
233 AccessibilityManager::PrefHandler::PrefHandler(const char* pref_path)
234 : pref_path_(pref_path) {}
236 AccessibilityManager::PrefHandler::~PrefHandler() {}
238 void AccessibilityManager::PrefHandler::HandleProfileChanged(
239 Profile* previous_profile, Profile* current_profile) {
240 // Returns if the current profile is null.
241 if (!current_profile)
242 return;
244 // If the user set a pref value on the login screen and is now starting a
245 // session with a new profile, copy the pref value to the profile.
246 if ((previous_profile &&
247 ProfileHelper::IsSigninProfile(previous_profile) &&
248 current_profile->IsNewProfile() &&
249 !ProfileHelper::IsSigninProfile(current_profile)) ||
250 // Special case for Guest mode:
251 // Guest mode launches a guest-mode browser process before session starts,
252 // so the previous profile is null.
253 (!previous_profile &&
254 current_profile->IsGuestSession())) {
255 // Returns if the pref has not been set by the user.
256 const PrefService::Preference* pref = ProfileHelper::GetSigninProfile()->
257 GetPrefs()->FindPreference(pref_path_);
258 if (!pref || !pref->IsUserControlled())
259 return;
261 // Copy the pref value from the signin screen.
262 const base::Value* value_on_login = pref->GetValue();
263 PrefService* user_prefs = current_profile->GetPrefs();
264 user_prefs->Set(pref_path_, *value_on_login);
268 ///////////////////////////////////////////////////////////////////////////////
270 // AccessibilityManager
272 // static
273 void AccessibilityManager::Initialize() {
274 CHECK(g_accessibility_manager == NULL);
275 g_accessibility_manager = new AccessibilityManager();
278 // static
279 void AccessibilityManager::Shutdown() {
280 CHECK(g_accessibility_manager);
281 delete g_accessibility_manager;
282 g_accessibility_manager = NULL;
285 // static
286 AccessibilityManager* AccessibilityManager::Get() {
287 return g_accessibility_manager;
290 AccessibilityManager::AccessibilityManager()
291 : profile_(NULL),
292 chrome_vox_loaded_on_lock_screen_(false),
293 chrome_vox_loaded_on_user_screen_(false),
294 large_cursor_pref_handler_(prefs::kLargeCursorEnabled),
295 spoken_feedback_pref_handler_(prefs::kSpokenFeedbackEnabled),
296 high_contrast_pref_handler_(prefs::kHighContrastEnabled),
297 autoclick_pref_handler_(prefs::kAutoclickEnabled),
298 autoclick_delay_pref_handler_(prefs::kAutoclickDelayMs),
299 large_cursor_enabled_(false),
300 sticky_keys_enabled_(false),
301 spoken_feedback_enabled_(false),
302 high_contrast_enabled_(false),
303 autoclick_enabled_(false),
304 autoclick_delay_ms_(ash::AutoclickController::kDefaultAutoclickDelayMs),
305 spoken_feedback_notification_(ash::A11Y_NOTIFICATION_NONE),
306 weak_ptr_factory_(this),
307 should_speak_chrome_vox_announcements_on_user_screen_(true),
308 system_sounds_enabled_(false) {
309 notification_registrar_.Add(this,
310 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
311 content::NotificationService::AllSources());
312 notification_registrar_.Add(this,
313 chrome::NOTIFICATION_SESSION_STARTED,
314 content::NotificationService::AllSources());
315 notification_registrar_.Add(this,
316 chrome::NOTIFICATION_PROFILE_DESTROYED,
317 content::NotificationService::AllSources());
318 notification_registrar_.Add(this,
319 chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED,
320 content::NotificationService::AllSources());
321 notification_registrar_.Add(this,
322 chrome::NOTIFICATION_EXTENSION_REMOVED,
323 content::NotificationService::AllSources());
324 notification_registrar_.Add(this,
325 chrome::NOTIFICATION_EXTENSION_UNLOADED,
326 content::NotificationService::AllSources());
328 GetBrailleController()->AddObserver(this);
330 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
331 media::SoundsManager* manager = media::SoundsManager::Get();
332 manager->Initialize(SOUND_SHUTDOWN,
333 bundle.GetRawDataResource(IDR_SOUND_SHUTDOWN_WAV));
334 manager->Initialize(
335 SOUND_SPOKEN_FEEDBACK_ENABLED,
336 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_ENABLED_WAV));
337 manager->Initialize(
338 SOUND_SPOKEN_FEEDBACK_DISABLED,
339 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_DISABLED_WAV));
342 AccessibilityManager::~AccessibilityManager() {
343 CHECK(this == g_accessibility_manager);
345 // Component extensions don't always notify us when they're unloaded. Ensure
346 // we clean up ChromeVox observers here.
347 for (std::set<Profile*>::iterator it = chromevox_profiles_.begin();
348 it != chromevox_profiles_.end();
349 it++) {
350 extensions::ExtensionSystem::Get(*it)->
351 event_router()->UnregisterObserver(this);
355 bool AccessibilityManager::ShouldShowAccessibilityMenu() {
356 // If any of the loaded profiles has an accessibility feature turned on - or
357 // enforced to always show the menu - we return true to show the menu.
358 std::vector<Profile*> profiles =
359 g_browser_process->profile_manager()->GetLoadedProfiles();
360 for (std::vector<Profile*>::iterator it = profiles.begin();
361 it != profiles.end();
362 ++it) {
363 PrefService* pref_service = (*it)->GetPrefs();
364 if (pref_service->GetBoolean(prefs::kStickyKeysEnabled) ||
365 pref_service->GetBoolean(prefs::kLargeCursorEnabled) ||
366 pref_service->GetBoolean(prefs::kSpokenFeedbackEnabled) ||
367 pref_service->GetBoolean(prefs::kHighContrastEnabled) ||
368 pref_service->GetBoolean(prefs::kAutoclickEnabled) ||
369 pref_service->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu) ||
370 pref_service->GetBoolean(prefs::kScreenMagnifierEnabled))
371 return true;
373 return false;
376 void AccessibilityManager::EnableLargeCursor(bool enabled) {
377 if (!profile_)
378 return;
380 PrefService* pref_service = profile_->GetPrefs();
381 pref_service->SetBoolean(prefs::kLargeCursorEnabled, enabled);
382 pref_service->CommitPendingWrite();
385 void AccessibilityManager::UpdateLargeCursorFromPref() {
386 if (!profile_)
387 return;
389 const bool enabled =
390 profile_->GetPrefs()->GetBoolean(prefs::kLargeCursorEnabled);
392 if (large_cursor_enabled_ == enabled)
393 return;
395 large_cursor_enabled_ = enabled;
397 AccessibilityStatusEventDetails details(enabled, ash::A11Y_NOTIFICATION_NONE);
398 content::NotificationService::current()->Notify(
399 chrome::NOTIFICATION_CROS_ACCESSIBILITY_TOGGLE_LARGE_CURSOR,
400 content::NotificationService::AllSources(),
401 content::Details<AccessibilityStatusEventDetails>(&details));
403 #if defined(USE_ASH)
404 // Large cursor is implemented only in ash.
405 ash::Shell::GetInstance()->cursor_manager()->SetCursorSet(
406 enabled ? ui::CURSOR_SET_LARGE : ui::CURSOR_SET_NORMAL);
407 #endif
410 bool AccessibilityManager::IsIncognitoAllowed() {
411 UserManager* user_manager = UserManager::Get();
412 // Supervised users can't create incognito-mode windows.
413 return !(user_manager->IsLoggedInAsLocallyManagedUser());
416 bool AccessibilityManager::IsLargeCursorEnabled() {
417 return large_cursor_enabled_;
420 void AccessibilityManager::EnableStickyKeys(bool enabled) {
421 if (!profile_)
422 return;
423 PrefService* pref_service = profile_->GetPrefs();
424 pref_service->SetBoolean(prefs::kStickyKeysEnabled, enabled);
425 pref_service->CommitPendingWrite();
428 bool AccessibilityManager::IsStickyKeysEnabled() {
429 return sticky_keys_enabled_;
432 void AccessibilityManager::UpdateStickyKeysFromPref() {
433 if (!profile_)
434 return;
436 const bool enabled =
437 profile_->GetPrefs()->GetBoolean(prefs::kStickyKeysEnabled);
439 if (sticky_keys_enabled_ == enabled)
440 return;
442 sticky_keys_enabled_ = enabled;
443 #if defined(USE_ASH)
444 // Sticky keys is implemented only in ash.
445 ash::Shell::GetInstance()->sticky_keys_controller()->Enable(enabled);
446 #endif
449 void AccessibilityManager::EnableSpokenFeedback(
450 bool enabled,
451 ash::AccessibilityNotificationVisibility notify) {
452 if (!profile_)
453 return;
455 ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(
456 enabled ? ash::UMA_STATUS_AREA_ENABLE_SPOKEN_FEEDBACK
457 : ash::UMA_STATUS_AREA_DISABLE_SPOKEN_FEEDBACK);
459 spoken_feedback_notification_ = notify;
461 PrefService* pref_service = profile_->GetPrefs();
462 pref_service->SetBoolean(
463 prefs::kSpokenFeedbackEnabled, enabled);
464 pref_service->CommitPendingWrite();
466 spoken_feedback_notification_ = ash::A11Y_NOTIFICATION_NONE;
469 void AccessibilityManager::UpdateSpokenFeedbackFromPref() {
470 if (!profile_)
471 return;
473 const bool enabled =
474 profile_->GetPrefs()->GetBoolean(prefs::kSpokenFeedbackEnabled);
476 if (spoken_feedback_enabled_ == enabled)
477 return;
479 spoken_feedback_enabled_ = enabled;
481 ExtensionAccessibilityEventRouter::GetInstance()->
482 SetAccessibilityEnabled(enabled);
484 AccessibilityStatusEventDetails details(enabled,
485 spoken_feedback_notification_);
486 content::NotificationService::current()->Notify(
487 chrome::NOTIFICATION_CROS_ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK,
488 content::NotificationService::AllSources(),
489 content::Details<AccessibilityStatusEventDetails>(&details));
491 if (enabled) {
492 LoadChromeVox();
493 } else {
494 ExtensionAccessibilityEventRouter::GetInstance()->
495 OnChromeVoxLoadStateChanged(profile_, false, false);
499 void AccessibilityManager::LoadChromeVox() {
500 SetUpPreLoadChromeVox(profile_);
502 ScreenLocker* screen_locker = ScreenLocker::default_screen_locker();
503 if (screen_locker && screen_locker->locked()) {
504 // If on the lock screen, loads ChromeVox only to the lock screen as for
505 // now. On unlock, it will be loaded to the user screen.
506 // (see. AccessibilityManager::Observe())
507 LoadChromeVoxToLockScreen();
508 return;
511 LoadChromeVoxToUserScreen();
514 void AccessibilityManager::LoadChromeVoxToUserScreen() {
515 if (chrome_vox_loaded_on_user_screen_)
516 return;
518 // Determine whether an OOBE screen is currently being shown. If so,
519 // ChromeVox will be injected directly into that screen.
520 content::WebUI* login_web_ui = NULL;
522 if (ProfileHelper::IsSigninProfile(profile_)) {
523 LoginDisplayHost* login_display_host = LoginDisplayHostImpl::default_host();
524 if (login_display_host) {
525 WebUILoginView* web_ui_login_view =
526 login_display_host->GetWebUILoginView();
527 if (web_ui_login_view)
528 login_web_ui = web_ui_login_view->GetWebUI();
532 LoadChromeVoxExtension(profile_, login_web_ui);
533 chrome_vox_loaded_on_user_screen_ = true;
536 void AccessibilityManager::LoadChromeVoxToLockScreen() {
537 if (chrome_vox_loaded_on_lock_screen_)
538 return;
540 ScreenLocker* screen_locker = ScreenLocker::default_screen_locker();
541 if (screen_locker && screen_locker->locked()) {
542 content::WebUI* lock_web_ui = screen_locker->GetAssociatedWebUI();
543 if (lock_web_ui) {
544 Profile* profile = Profile::FromWebUI(lock_web_ui);
545 LoadChromeVoxExtension(profile, lock_web_ui);
546 chrome_vox_loaded_on_lock_screen_ = true;
551 void AccessibilityManager::UnloadChromeVox() {
552 if (system_sounds_enabled_) {
553 ash::PlaySystemSound(SOUND_SPOKEN_FEEDBACK_DISABLED,
554 false /* honor_spoken_feedback */);
556 if (chrome_vox_loaded_on_lock_screen_)
557 UnloadChromeVoxFromLockScreen();
559 if (chrome_vox_loaded_on_user_screen_) {
560 UnloadChromeVoxExtension(profile_);
561 chrome_vox_loaded_on_user_screen_ = false;
565 void AccessibilityManager::UnloadChromeVoxFromLockScreen() {
566 // Lock screen uses the signin progile.
567 Profile* signin_profile = ProfileHelper::GetSigninProfile();
568 UnloadChromeVoxExtension(signin_profile);
569 chrome_vox_loaded_on_lock_screen_ = false;
572 bool AccessibilityManager::IsSpokenFeedbackEnabled() {
573 return spoken_feedback_enabled_;
576 void AccessibilityManager::ToggleSpokenFeedback(
577 ash::AccessibilityNotificationVisibility notify) {
578 EnableSpokenFeedback(!IsSpokenFeedbackEnabled(), notify);
581 void AccessibilityManager::EnableHighContrast(bool enabled) {
582 if (!profile_)
583 return;
585 PrefService* pref_service = profile_->GetPrefs();
586 pref_service->SetBoolean(prefs::kHighContrastEnabled, enabled);
587 pref_service->CommitPendingWrite();
590 void AccessibilityManager::UpdateHighContrastFromPref() {
591 if (!profile_)
592 return;
594 const bool enabled =
595 profile_->GetPrefs()->GetBoolean(prefs::kHighContrastEnabled);
597 if (high_contrast_enabled_ == enabled)
598 return;
600 high_contrast_enabled_ = enabled;
602 AccessibilityStatusEventDetails detail(enabled, ash::A11Y_NOTIFICATION_NONE);
603 content::NotificationService::current()->Notify(
604 chrome::NOTIFICATION_CROS_ACCESSIBILITY_TOGGLE_HIGH_CONTRAST_MODE,
605 content::NotificationService::AllSources(),
606 content::Details<AccessibilityStatusEventDetails>(&detail));
608 #if defined(USE_ASH)
609 ash::Shell::GetInstance()->high_contrast_controller()->SetEnabled(enabled);
610 #endif
613 void AccessibilityManager::LocalePrefChanged() {
614 if (!profile_)
615 return;
617 if (!IsSpokenFeedbackEnabled())
618 return;
620 // If the system locale changes and spoken feedback is enabled,
621 // reload ChromeVox so that it switches its internal translations
622 // to the new language.
623 EnableSpokenFeedback(false, ash::A11Y_NOTIFICATION_NONE);
624 EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_NONE);
627 bool AccessibilityManager::IsHighContrastEnabled() {
628 return high_contrast_enabled_;
631 void AccessibilityManager::EnableAutoclick(bool enabled) {
632 if (!profile_)
633 return;
635 PrefService* pref_service = profile_->GetPrefs();
636 pref_service->SetBoolean(prefs::kAutoclickEnabled, enabled);
637 pref_service->CommitPendingWrite();
640 bool AccessibilityManager::IsAutoclickEnabled() {
641 return autoclick_enabled_;
644 void AccessibilityManager::UpdateAutoclickFromPref() {
645 bool enabled =
646 profile_->GetPrefs()->GetBoolean(prefs::kAutoclickEnabled);
648 if (autoclick_enabled_ == enabled)
649 return;
650 autoclick_enabled_ = enabled;
652 #if defined(USE_ASH)
653 ash::Shell::GetInstance()->autoclick_controller()->SetEnabled(enabled);
654 #endif
657 void AccessibilityManager::SetAutoclickDelay(int delay_ms) {
658 if (!profile_)
659 return;
661 PrefService* pref_service = profile_->GetPrefs();
662 pref_service->SetInteger(prefs::kAutoclickDelayMs, delay_ms);
663 pref_service->CommitPendingWrite();
666 int AccessibilityManager::GetAutoclickDelay() const {
667 return autoclick_delay_ms_;
670 void AccessibilityManager::UpdateAutoclickDelayFromPref() {
671 int autoclick_delay_ms =
672 profile_->GetPrefs()->GetInteger(prefs::kAutoclickDelayMs);
674 if (autoclick_delay_ms == autoclick_delay_ms_)
675 return;
676 autoclick_delay_ms_ = autoclick_delay_ms;
678 #if defined(USE_ASH)
679 ash::Shell::GetInstance()->autoclick_controller()->SetAutoclickDelay(
680 autoclick_delay_ms_);
681 #endif
684 void AccessibilityManager::CheckBrailleState() {
685 BrowserThread::PostTaskAndReplyWithResult(
686 BrowserThread::IO, FROM_HERE, base::Bind(
687 &BrailleController::GetDisplayState,
688 base::Unretained(GetBrailleController())),
689 base::Bind(&AccessibilityManager::ReceiveBrailleDisplayState,
690 weak_ptr_factory_.GetWeakPtr()));
693 void AccessibilityManager::ReceiveBrailleDisplayState(
694 scoped_ptr<extensions::api::braille_display_private::DisplayState> state) {
695 OnDisplayStateChanged(*state);
699 void AccessibilityManager::SetProfile(Profile* profile) {
700 pref_change_registrar_.reset();
701 local_state_pref_change_registrar_.reset();
703 if (profile) {
704 // TODO(yoshiki): Move following code to PrefHandler.
705 pref_change_registrar_.reset(new PrefChangeRegistrar);
706 pref_change_registrar_->Init(profile->GetPrefs());
707 pref_change_registrar_->Add(
708 prefs::kLargeCursorEnabled,
709 base::Bind(&AccessibilityManager::UpdateLargeCursorFromPref,
710 base::Unretained(this)));
711 pref_change_registrar_->Add(
712 prefs::kStickyKeysEnabled,
713 base::Bind(&AccessibilityManager::UpdateStickyKeysFromPref,
714 base::Unretained(this)));
715 pref_change_registrar_->Add(
716 prefs::kSpokenFeedbackEnabled,
717 base::Bind(&AccessibilityManager::UpdateSpokenFeedbackFromPref,
718 base::Unretained(this)));
719 pref_change_registrar_->Add(
720 prefs::kHighContrastEnabled,
721 base::Bind(&AccessibilityManager::UpdateHighContrastFromPref,
722 base::Unretained(this)));
723 pref_change_registrar_->Add(
724 prefs::kAutoclickEnabled,
725 base::Bind(&AccessibilityManager::UpdateAutoclickFromPref,
726 base::Unretained(this)));
727 pref_change_registrar_->Add(
728 prefs::kAutoclickDelayMs,
729 base::Bind(&AccessibilityManager::UpdateAutoclickDelayFromPref,
730 base::Unretained(this)));
732 local_state_pref_change_registrar_.reset(new PrefChangeRegistrar);
733 local_state_pref_change_registrar_->Init(g_browser_process->local_state());
734 local_state_pref_change_registrar_->Add(
735 prefs::kApplicationLocale,
736 base::Bind(&AccessibilityManager::LocalePrefChanged,
737 base::Unretained(this)));
739 content::BrowserAccessibilityState::GetInstance()->AddHistogramCallback(
740 base::Bind(
741 &AccessibilityManager::UpdateChromeOSAccessibilityHistograms,
742 base::Unretained(this)));
745 large_cursor_pref_handler_.HandleProfileChanged(profile_, profile);
746 spoken_feedback_pref_handler_.HandleProfileChanged(profile_, profile);
747 high_contrast_pref_handler_.HandleProfileChanged(profile_, profile);
748 autoclick_pref_handler_.HandleProfileChanged(profile_, profile);
749 autoclick_delay_pref_handler_.HandleProfileChanged(profile_, profile);
751 if (profile && spoken_feedback_enabled_)
752 SetUpPreLoadChromeVox(profile);
754 if (!profile_ && profile)
755 CheckBrailleState();
757 profile_ = profile;
758 UpdateLargeCursorFromPref();
759 UpdateStickyKeysFromPref();
760 UpdateSpokenFeedbackFromPref();
761 UpdateHighContrastFromPref();
762 UpdateAutoclickFromPref();
763 UpdateAutoclickDelayFromPref();
766 void AccessibilityManager::ActiveUserChanged(const std::string& user_id) {
767 SetProfile(ProfileManager::GetActiveUserProfile());
770 void AccessibilityManager::SetProfileForTest(Profile* profile) {
771 SetProfile(profile);
774 void AccessibilityManager::SetBrailleControllerForTest(
775 BrailleController* controller) {
776 g_braille_controller_for_test = controller;
779 void AccessibilityManager::EnableSystemSounds(bool system_sounds_enabled) {
780 system_sounds_enabled_ = system_sounds_enabled;
783 base::TimeDelta AccessibilityManager::PlayShutdownSound() {
784 if (!system_sounds_enabled_)
785 return base::TimeDelta();
786 system_sounds_enabled_ = false;
787 if (!ash::PlaySystemSound(SOUND_SHUTDOWN, true /* honor_spoken_feedback */))
788 return base::TimeDelta();
789 return media::SoundsManager::Get()->GetDuration(SOUND_SHUTDOWN);
792 void AccessibilityManager::UpdateChromeOSAccessibilityHistograms() {
793 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosSpokenFeedback",
794 IsSpokenFeedbackEnabled());
795 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosHighContrast",
796 IsHighContrastEnabled());
797 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosVirtualKeyboard",
798 accessibility::IsVirtualKeyboardEnabled());
799 if (MagnificationManager::Get()) {
800 uint32 type = MagnificationManager::Get()->IsMagnifierEnabled() ?
801 MagnificationManager::Get()->GetMagnifierType() : 0;
802 // '0' means magnifier is disabled.
803 UMA_HISTOGRAM_ENUMERATION("Accessibility.CrosScreenMagnifier",
804 type,
805 ash::kMaxMagnifierType + 1);
807 if (profile_) {
808 const PrefService* const prefs = profile_->GetPrefs();
809 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosLargeCursor",
810 prefs->GetBoolean(prefs::kLargeCursorEnabled));
811 UMA_HISTOGRAM_BOOLEAN(
812 "Accessibility.CrosAlwaysShowA11yMenu",
813 prefs->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu));
815 bool autoclick_enabled = prefs->GetBoolean(prefs::kAutoclickEnabled);
816 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosAutoclick", autoclick_enabled);
817 if (autoclick_enabled) {
818 // We only want to log the autoclick delay if the user has actually
819 // enabled autoclick.
820 UMA_HISTOGRAM_CUSTOM_TIMES(
821 "Accessibility.CrosAutoclickDelay",
822 base::TimeDelta::FromMilliseconds(
823 prefs->GetInteger(prefs::kAutoclickDelayMs)),
824 base::TimeDelta::FromMilliseconds(1),
825 base::TimeDelta::FromMilliseconds(3000),
826 50);
831 void AccessibilityManager::Observe(
832 int type,
833 const content::NotificationSource& source,
834 const content::NotificationDetails& details) {
835 switch (type) {
836 case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE: {
837 // Update |profile_| when entering the login screen.
838 Profile* profile = ProfileManager::GetActiveUserProfile();
839 if (ProfileHelper::IsSigninProfile(profile))
840 SetProfile(profile);
841 break;
843 case chrome::NOTIFICATION_SESSION_STARTED:
844 // Update |profile_| when entering a session.
845 SetProfile(ProfileManager::GetActiveUserProfile());
847 // Ensure ChromeVox makes announcements at the start of new sessions.
848 should_speak_chrome_vox_announcements_on_user_screen_ = true;
850 // Add a session state observer to be able to monitor session changes.
851 if (!session_state_observer_.get() && ash::Shell::HasInstance())
852 session_state_observer_.reset(
853 new ash::ScopedSessionStateObserver(this));
854 break;
855 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
856 // Update |profile_| when exiting a session or shutting down.
857 Profile* profile = content::Source<Profile>(source).ptr();
858 if (profile_ == profile)
859 SetProfile(NULL);
861 if (IsSpokenFeedbackEnabled())
862 TearDownPostUnloadChromeVox(profile);
863 break;
865 case chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED: {
866 bool is_screen_locked = *content::Details<bool>(details).ptr();
867 if (spoken_feedback_enabled_) {
868 if (is_screen_locked) {
869 LoadChromeVoxToLockScreen();
871 // Status tray gets verbalized by user screen ChromeVox, so we need
872 // this as well.
873 LoadChromeVoxToUserScreen();
874 } else {
875 // Lock screen destroys its resources; no need for us to explicitly
876 // unload ChromeVox.
877 chrome_vox_loaded_on_lock_screen_ = false;
879 // However, if spoken feedback was enabled, also enable it on the user
880 // screen.
881 LoadChromeVoxToUserScreen();
884 break;
886 case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
887 extensions::UnloadedExtensionInfo* info =
888 content::Details<extensions::UnloadedExtensionInfo>(details).ptr();
889 const extensions::Extension* extension = info->extension;
890 if (extension->id() == extension_misc::kChromeVoxExtensionId) {
891 Profile* profile = content::Source<Profile>(source).ptr();
892 TearDownPostUnloadChromeVox(profile);
894 break;
896 case chrome::NOTIFICATION_EXTENSION_REMOVED: {
897 const extensions::Extension* extension =
898 content::Details<const extensions::Extension>(details).ptr();
899 if (extension->id() == extension_misc::kChromeVoxExtensionId) {
900 Profile* profile = content::Source<Profile>(source).ptr();
901 TearDownPostUnloadChromeVox(profile);
903 break;
908 void AccessibilityManager::OnDisplayStateChanged(
909 const DisplayState& display_state) {
910 if (display_state.available)
911 EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_SHOW);
914 void AccessibilityManager::OnListenerAdded(
915 const extensions::EventListenerInfo& details) {
916 if (details.extension_id != extension_misc::kChromeVoxExtensionId)
917 return;
919 ExtensionAccessibilityEventRouter::GetInstance()->
920 OnChromeVoxLoadStateChanged(profile_,
921 IsSpokenFeedbackEnabled(),
922 chrome_vox_loaded_on_lock_screen_ ||
923 should_speak_chrome_vox_announcements_on_user_screen_);
925 should_speak_chrome_vox_announcements_on_user_screen_ =
926 chrome_vox_loaded_on_lock_screen_;
929 void AccessibilityManager::OnListenerRemoved(
930 const extensions::EventListenerInfo& details) {
931 if (details.extension_id != extension_misc::kChromeVoxExtensionId)
932 return;
934 UnloadChromeVox();
936 // It's possible for a user to rapidly toggle ChromeVox on/off state. Load
937 // ChromeVox again if we've been enabled while disabling.
938 if (IsSpokenFeedbackEnabled())
939 LoadChromeVox();
942 void AccessibilityManager::SetUpPreLoadChromeVox(Profile* profile) {
943 // Do any setup work needed immediately before ChromeVox actually loads.
944 if (system_sounds_enabled_) {
945 ash::PlaySystemSound(SOUND_SPOKEN_FEEDBACK_ENABLED,
946 false /* honor_spoken_feedback */);
949 if (profile) {
950 extensions::ExtensionSystem::Get(profile)->
951 event_router()->RegisterObserver(this,
952 extensions::api::experimental_accessibility::
953 OnChromeVoxLoadStateChanged::kEventName);
954 chromevox_profiles_.insert(profile);
958 void AccessibilityManager::TearDownPostUnloadChromeVox(Profile* profile) {
959 // Do any teardown work needed immediately after ChromeVox actually unloads.
960 if (profile) {
961 extensions::ExtensionSystem::Get(profile)->
962 event_router()->UnregisterObserver(this);
963 chromevox_profiles_.erase(profile);
967 } // namespace chromeos