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
63 using content::BrowserThread;
64 using content::RenderViewHost;
65 using extensions::api::braille_display_private::BrailleController;
66 using extensions::api::braille_display_private::DisplayState;
67
68 namespace chromeos {
69
70 namespace {
71
72 static chromeos::AccessibilityManager* g_accessibility_manager = NULL;
73
74 static BrailleController* g_braille_controller_for_test = NULL;
75
GetBrailleController()76 BrailleController* GetBrailleController() {
77 return g_braille_controller_for_test
78 ? g_braille_controller_for_test
79 : BrailleController::GetInstance();
80 }
81
GetChromeVoxPath()82 base::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.
92 class ContentScriptLoader {
93 public:
94 // Initialize the ContentScriptLoader with the ID of the extension
95 // and the RenderViewHost where the scripts should be loaded.
ContentScriptLoader(const std::string & extension_id,int render_process_id,int render_view_id)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.
AppendScript(extensions::ExtensionResource resource)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.
Run()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:
OnFileLoaded(bool success,const std::string & data)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
LoadChromeVoxExtension(Profile * profile,content::WebUI * login_web_ui)152 void 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
UnloadChromeVoxExtension(Profile * profile)198 void 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
AccessibilityStatusEventDetails(bool enabled,ash::AccessibilityNotificationVisibility notify)210 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
211 bool enabled,
212 ash::AccessibilityNotificationVisibility notify)
213 : enabled(enabled),
214 magnifier_type(ash::kDefaultMagnifierType),
215 notify(notify) {}
216
AccessibilityStatusEventDetails(bool enabled,ash::MagnifierType magnifier_type,ash::AccessibilityNotificationVisibility notify)217 AccessibilityStatusEventDetails::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
PrefHandler(const char * pref_path)229 AccessibilityManager::PrefHandler::PrefHandler(const char* pref_path)
230 : pref_path_(pref_path) {}
231
~PrefHandler()232 AccessibilityManager::PrefHandler::~PrefHandler() {}
233
HandleProfileChanged(Profile * previous_profile,Profile * current_profile)234 void 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
Initialize()269 void AccessibilityManager::Initialize() {
270 CHECK(g_accessibility_manager == NULL);
271 g_accessibility_manager = new AccessibilityManager();
272 }
273
274 // static
Shutdown()275 void AccessibilityManager::Shutdown() {
276 CHECK(g_accessibility_manager);
277 delete g_accessibility_manager;
278 g_accessibility_manager = NULL;
279 }
280
281 // static
Get()282 AccessibilityManager* AccessibilityManager::Get() {
283 return g_accessibility_manager;
284 }
285
AccessibilityManager()286 AccessibilityManager::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
~AccessibilityManager()332 AccessibilityManager::~AccessibilityManager() {
333 CHECK(this == g_accessibility_manager);
334 }
335
ShouldShowAccessibilityMenu()336 bool 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
EnableLargeCursor(bool enabled)357 void 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
UpdateLargeCursorFromPref()366 void 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
IsIncognitoAllowed()391 bool AccessibilityManager::IsIncognitoAllowed() {
392 UserManager* user_manager = UserManager::Get();
393 // Supervised users can't create incognito-mode windows.
394 return !(user_manager->IsLoggedInAsLocallyManagedUser());
395 }
396
IsLargeCursorEnabled()397 bool AccessibilityManager::IsLargeCursorEnabled() {
398 return large_cursor_enabled_;
399 }
400
EnableStickyKeys(bool enabled)401 void 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
IsStickyKeysEnabled()409 bool AccessibilityManager::IsStickyKeysEnabled() {
410 return sticky_keys_enabled_;
411 }
412
UpdateStickyKeysFromPref()413 void 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
EnableSpokenFeedback(bool enabled,ash::AccessibilityNotificationVisibility notify)430 void 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
UpdateSpokenFeedbackFromPref()450 void 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
LoadChromeVox()479 void 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
LoadChromeVoxToUserScreen()492 void 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
LoadChromeVoxToLockScreen()514 void 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
UnloadChromeVox()529 void 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
UnloadChromeVoxFromLockScreen()541 void 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
IsSpokenFeedbackEnabled()548 bool AccessibilityManager::IsSpokenFeedbackEnabled() {
549 return spoken_feedback_enabled_;
550 }
551
ToggleSpokenFeedback(ash::AccessibilityNotificationVisibility notify)552 void AccessibilityManager::ToggleSpokenFeedback(
553 ash::AccessibilityNotificationVisibility notify) {
554 EnableSpokenFeedback(!IsSpokenFeedbackEnabled(), notify);
555 }
556
EnableHighContrast(bool enabled)557 void 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
UpdateHighContrastFromPref()566 void 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
LocalePrefChanged()589 void 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
IsHighContrastEnabled()603 bool AccessibilityManager::IsHighContrastEnabled() {
604 return high_contrast_enabled_;
605 }
606
EnableAutoclick(bool enabled)607 void 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
IsAutoclickEnabled()616 bool AccessibilityManager::IsAutoclickEnabled() {
617 return autoclick_enabled_;
618 }
619
UpdateAutoclickFromPref()620 void 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
SetAutoclickDelay(int delay_ms)633 void 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
GetAutoclickDelay() const642 int AccessibilityManager::GetAutoclickDelay() const {
643 return autoclick_delay_ms_;
644 }
645
UpdateAutoclickDelayFromPref()646 void 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
CheckBrailleState()660 void 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
ReceiveBrailleDisplayState(scoped_ptr<extensions::api::braille_display_private::DisplayState> state)669 void AccessibilityManager::ReceiveBrailleDisplayState(
670 scoped_ptr<extensions::api::braille_display_private::DisplayState> state) {
671 OnDisplayStateChanged(*state);
672 }
673
674
SetProfile(Profile * profile)675 void 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
ActiveUserChanged(const std::string & user_id)739 void AccessibilityManager::ActiveUserChanged(const std::string& user_id) {
740 SetProfile(ProfileManager::GetActiveUserProfile());
741 }
742
SetProfileForTest(Profile * profile)743 void AccessibilityManager::SetProfileForTest(Profile* profile) {
744 SetProfile(profile);
745 }
746
SetBrailleControllerForTest(BrailleController * controller)747 void AccessibilityManager::SetBrailleControllerForTest(
748 BrailleController* controller) {
749 g_braille_controller_for_test = controller;
750 }
751
EnableSystemSounds(bool system_sounds_enabled)752 void AccessibilityManager::EnableSystemSounds(bool system_sounds_enabled) {
753 system_sounds_enabled_ = system_sounds_enabled;
754 }
755
PlayShutdownSound()756 base::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
UpdateChromeOSAccessibilityHistograms()765 void 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
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)804 void 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
OnDisplayStateChanged(const DisplayState & display_state)859 void AccessibilityManager::OnDisplayStateChanged(
860 const DisplayState& display_state) {
861 if (display_state.available)
862 EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_SHOW);
863 }
864
PostLoadChromeVox(Profile * profile)865 void 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
PostUnloadChromeVox(Profile * profile)878 void AccessibilityManager::PostUnloadChromeVox(Profile* profile) {
879 // Do any teardown work needed immediately after ChromeVox actually unloads.
880 PlaySound(SOUND_SPOKEN_FEEDBACK_DISABLED);
881 }
882
PlaySound(int sound_key) const883 void AccessibilityManager::PlaySound(int sound_key) const {
884 media::SoundsManager::Get()->Play(sound_key);
885 }
886
887 } // namespace chromeos
888