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 "base/callback.h"
8 #include "base/callback_helpers.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/memory/singleton.h"
11 #include "base/metrics/histogram.h"
12 #include "base/path_service.h"
13 #include "base/prefs/pref_member.h"
14 #include "base/prefs/pref_service.h"
15 #include "base/strings/string_split.h"
16 #include "base/strings/string_util.h"
17 #include "base/time/time.h"
18 #include "base/values.h"
19 #include "chrome/browser/accessibility/accessibility_extension_api.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/chromeos/login/lock/screen_locker.h"
23 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
24 #include "chrome/browser/chromeos/login/ui/login_display_host_impl.h"
25 #include "chrome/browser/chromeos/login/ui/webui_login_view.h"
26 #include "chrome/browser/chromeos/profiles/profile_helper.h"
27 #include "chrome/browser/chromeos/ui/accessibility_focus_ring_controller.h"
28 #include "chrome/browser/extensions/component_loader.h"
29 #include "chrome/browser/extensions/extension_service.h"
30 #include "chrome/browser/profiles/profile.h"
31 #include "chrome/browser/profiles/profile_manager.h"
32 #include "chrome/common/chrome_paths.h"
33 #include "chrome/common/extensions/api/accessibility_private.h"
34 #include "chrome/common/extensions/extension_constants.h"
35 #include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h"
36 #include "chrome/common/pref_names.h"
37 #include "chrome/grit/browser_resources.h"
38 #include "chromeos/audio/chromeos_sounds.h"
39 #include "chromeos/ime/input_method_manager.h"
40 #include "chromeos/login/login_state.h"
41 #include "components/user_manager/user_manager.h"
42 #include "content/public/browser/browser_accessibility_state.h"
43 #include "content/public/browser/browser_thread.h"
44 #include "content/public/browser/notification_details.h"
45 #include "content/public/browser/notification_service.h"
46 #include "content/public/browser/notification_source.h"
47 #include "content/public/browser/render_process_host.h"
48 #include "content/public/browser/render_view_host.h"
49 #include "content/public/browser/web_contents.h"
50 #include "content/public/browser/web_ui.h"
51 #include "extensions/browser/extension_system.h"
52 #include "extensions/browser/file_reader.h"
53 #include "extensions/common/extension.h"
54 #include "extensions/common/extension_messages.h"
55 #include "extensions/common/extension_resource.h"
56 #include "media/audio/sounds/sounds_manager.h"
57 #include "ui/base/resource/resource_bundle.h"
58 #include "ui/keyboard/keyboard_controller.h"
59 #include "ui/keyboard/keyboard_util.h"
61 #if !defined(USE_ATHENA)
62 #include "ash/audio/sounds.h"
63 #include "ash/autoclick/autoclick_controller.h"
64 #include "ash/high_contrast/high_contrast_controller.h"
65 #include "ash/metrics/user_metrics_recorder.h"
66 #include "ash/session/session_state_delegate.h"
67 #include "ash/shell.h"
68 #include "ash/sticky_keys/sticky_keys_controller.h"
69 #include "ash/system/tray/system_tray_notifier.h"
70 #include "chrome/browser/chromeos/accessibility/magnification_manager.h"
73 using content::BrowserThread
;
74 using content::RenderViewHost
;
75 using extensions::api::braille_display_private::BrailleController
;
76 using extensions::api::braille_display_private::DisplayState
;
77 using extensions::api::braille_display_private::KeyEvent
;
83 static chromeos::AccessibilityManager
* g_accessibility_manager
= NULL
;
85 static BrailleController
* g_braille_controller_for_test
= NULL
;
87 BrailleController
* GetBrailleController() {
88 return g_braille_controller_for_test
89 ? g_braille_controller_for_test
90 : BrailleController::GetInstance();
93 base::FilePath
GetChromeVoxPath() {
95 if (!PathService::Get(chrome::DIR_RESOURCES
, &path
))
97 path
= path
.Append(extension_misc::kChromeVoxExtensionPath
);
101 // Helper class that directly loads an extension's content scripts into
102 // all of the frames corresponding to a given RenderViewHost.
103 class ContentScriptLoader
{
105 // Initialize the ContentScriptLoader with the ID of the extension
106 // and the RenderViewHost where the scripts should be loaded.
107 ContentScriptLoader(const std::string
& extension_id
,
108 int render_process_id
,
110 : extension_id_(extension_id
),
111 render_process_id_(render_process_id
),
112 render_view_id_(render_view_id
) {}
114 // Call this once with the ExtensionResource corresponding to each
115 // content script to be loaded.
116 void AppendScript(extensions::ExtensionResource resource
) {
117 resources_
.push(resource
);
120 // Finally, call this method once to fetch all of the resources and
121 // load them. This method will delete this object when done.
123 if (resources_
.empty()) {
128 extensions::ExtensionResource resource
= resources_
.front();
130 scoped_refptr
<FileReader
> reader(new FileReader(resource
, base::Bind(
131 &ContentScriptLoader::OnFileLoaded
, base::Unretained(this))));
136 void OnFileLoaded(bool success
, const std::string
& data
) {
138 ExtensionMsg_ExecuteCode_Params params
;
139 params
.request_id
= 0;
140 params
.extension_id
= extension_id_
;
141 params
.is_javascript
= true;
143 params
.run_at
= extensions::UserScript::DOCUMENT_IDLE
;
144 params
.all_frames
= true;
145 params
.match_about_blank
= false;
146 params
.in_main_world
= false;
148 RenderViewHost
* render_view_host
=
149 RenderViewHost::FromID(render_process_id_
, render_view_id_
);
150 if (render_view_host
) {
151 render_view_host
->Send(new ExtensionMsg_ExecuteCode(
152 render_view_host
->GetRoutingID(), params
));
158 std::string extension_id_
;
159 int render_process_id_
;
161 std::queue
<extensions::ExtensionResource
> resources_
;
164 void InjectChromeVoxContentScript(
165 ExtensionService
* extension_service
,
166 int render_process_id
,
168 const base::Closure
& done_cb
);
170 void LoadChromeVoxExtension(
172 RenderViewHost
* render_view_host
,
173 base::Closure done_cb
) {
174 ExtensionService
* extension_service
=
175 extensions::ExtensionSystem::Get(profile
)->extension_service();
176 if (render_view_host
) {
177 // Wrap the passed in callback to inject the content script.
178 done_cb
= base::Bind(
179 &InjectChromeVoxContentScript
,
181 render_view_host
->GetProcess()->GetID(),
182 render_view_host
->GetRoutingID(),
185 extension_service
->component_loader()->AddChromeVoxExtension(done_cb
);
188 void InjectChromeVoxContentScript(
189 ExtensionService
* extension_service
,
190 int render_process_id
,
192 const base::Closure
& done_cb
) {
193 // Make sure to always run |done_cb|. ChromeVox was loaded even if we end up
194 // not injecting into this particular render view.
195 base::ScopedClosureRunner
done_runner(done_cb
);
196 RenderViewHost
* render_view_host
=
197 RenderViewHost::FromID(render_process_id
, render_view_id
);
198 if (!render_view_host
)
200 const extensions::Extension
* extension
=
201 extension_service
->extensions()->GetByID(
202 extension_misc::kChromeVoxExtensionId
);
204 // Set a flag to tell ChromeVox that it's just been enabled,
205 // so that it won't interrupt our speech feedback enabled message.
206 ExtensionMsg_ExecuteCode_Params params
;
207 params
.request_id
= 0;
208 params
.extension_id
= extension
->id();
209 params
.is_javascript
= true;
210 params
.code
= "window.INJECTED_AFTER_LOAD = true;";
211 params
.run_at
= extensions::UserScript::DOCUMENT_IDLE
;
212 params
.all_frames
= true;
213 params
.match_about_blank
= false;
214 params
.in_main_world
= false;
215 render_view_host
->Send(new ExtensionMsg_ExecuteCode(
216 render_view_host
->GetRoutingID(), params
));
218 // Inject ChromeVox' content scripts.
219 ContentScriptLoader
* loader
= new ContentScriptLoader(
220 extension
->id(), render_view_host
->GetProcess()->GetID(),
221 render_view_host
->GetRoutingID());
223 const extensions::UserScriptList
& content_scripts
=
224 extensions::ContentScriptsInfo::GetContentScripts(extension
);
225 for (size_t i
= 0; i
< content_scripts
.size(); i
++) {
226 const extensions::UserScript
& script
= content_scripts
[i
];
227 for (size_t j
= 0; j
< script
.js_scripts().size(); ++j
) {
228 const extensions::UserScript::File
& file
= script
.js_scripts()[j
];
229 extensions::ExtensionResource resource
= extension
->GetResource(
230 file
.relative_path());
231 loader
->AppendScript(resource
);
234 loader
->Run(); // It cleans itself up when done.
237 void UnloadChromeVoxExtension(Profile
* profile
) {
238 base::FilePath path
= GetChromeVoxPath();
239 ExtensionService
* extension_service
=
240 extensions::ExtensionSystem::Get(profile
)->extension_service();
241 extension_service
->component_loader()->Remove(path
);
246 ///////////////////////////////////////////////////////////////////////////////
247 // AccessibilityStatusEventDetails
249 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
250 AccessibilityNotificationType notification_type
,
252 ui::AccessibilityNotificationVisibility notify
)
253 : notification_type(notification_type
),
255 magnifier_type(ui::kDefaultMagnifierType
),
258 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
259 AccessibilityNotificationType notification_type
,
261 ui::MagnifierType magnifier_type
,
262 ui::AccessibilityNotificationVisibility notify
)
263 : notification_type(notification_type
),
265 magnifier_type(magnifier_type
),
268 ///////////////////////////////////////////////////////////////////////////////
270 // AccessibilityManager::PrefHandler
272 AccessibilityManager::PrefHandler::PrefHandler(const char* pref_path
)
273 : pref_path_(pref_path
) {}
275 AccessibilityManager::PrefHandler::~PrefHandler() {}
277 void AccessibilityManager::PrefHandler::HandleProfileChanged(
278 Profile
* previous_profile
, Profile
* current_profile
) {
279 // Returns if the current profile is null.
280 if (!current_profile
)
283 // If the user set a pref value on the login screen and is now starting a
284 // session with a new profile, copy the pref value to the profile.
285 if ((previous_profile
&&
286 ProfileHelper::IsSigninProfile(previous_profile
) &&
287 current_profile
->IsNewProfile() &&
288 !ProfileHelper::IsSigninProfile(current_profile
)) ||
289 // Special case for Guest mode:
290 // Guest mode launches a guest-mode browser process before session starts,
291 // so the previous profile is null.
292 (!previous_profile
&&
293 current_profile
->IsGuestSession())) {
294 // Returns if the pref has not been set by the user.
295 const PrefService::Preference
* pref
= ProfileHelper::GetSigninProfile()->
296 GetPrefs()->FindPreference(pref_path_
);
297 if (!pref
|| !pref
->IsUserControlled())
300 // Copy the pref value from the signin screen.
301 const base::Value
* value_on_login
= pref
->GetValue();
302 PrefService
* user_prefs
= current_profile
->GetPrefs();
303 user_prefs
->Set(pref_path_
, *value_on_login
);
307 ///////////////////////////////////////////////////////////////////////////////
309 // AccessibilityManager
312 void AccessibilityManager::Initialize() {
313 CHECK(g_accessibility_manager
== NULL
);
314 g_accessibility_manager
= new AccessibilityManager();
318 void AccessibilityManager::Shutdown() {
319 CHECK(g_accessibility_manager
);
320 delete g_accessibility_manager
;
321 g_accessibility_manager
= NULL
;
325 AccessibilityManager
* AccessibilityManager::Get() {
326 return g_accessibility_manager
;
329 AccessibilityManager::AccessibilityManager()
331 chrome_vox_loaded_on_lock_screen_(false),
332 chrome_vox_loaded_on_user_screen_(false),
333 large_cursor_pref_handler_(prefs::kAccessibilityLargeCursorEnabled
),
334 spoken_feedback_pref_handler_(prefs::kAccessibilitySpokenFeedbackEnabled
),
335 high_contrast_pref_handler_(prefs::kAccessibilityHighContrastEnabled
),
336 autoclick_pref_handler_(prefs::kAccessibilityAutoclickEnabled
),
337 autoclick_delay_pref_handler_(prefs::kAccessibilityAutoclickDelayMs
),
338 virtual_keyboard_pref_handler_(
339 prefs::kAccessibilityVirtualKeyboardEnabled
),
340 large_cursor_enabled_(false),
341 sticky_keys_enabled_(false),
342 spoken_feedback_enabled_(false),
343 high_contrast_enabled_(false),
344 autoclick_enabled_(false),
345 #if defined(USE_ATHENA)
346 autoclick_delay_ms_(400),
348 autoclick_delay_ms_(ash::AutoclickController::kDefaultAutoclickDelayMs
),
350 virtual_keyboard_enabled_(false),
351 spoken_feedback_notification_(ui::A11Y_NOTIFICATION_NONE
),
352 should_speak_chrome_vox_announcements_on_user_screen_(true),
353 system_sounds_enabled_(false),
354 braille_display_connected_(false),
355 scoped_braille_observer_(this),
356 braille_ime_current_(false),
357 weak_ptr_factory_(this) {
358 notification_registrar_
.Add(this,
359 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE
,
360 content::NotificationService::AllSources());
361 notification_registrar_
.Add(this,
362 chrome::NOTIFICATION_SESSION_STARTED
,
363 content::NotificationService::AllSources());
364 notification_registrar_
.Add(this,
365 chrome::NOTIFICATION_PROFILE_DESTROYED
,
366 content::NotificationService::AllSources());
367 notification_registrar_
.Add(this,
368 chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED
,
369 content::NotificationService::AllSources());
371 input_method::InputMethodManager::Get()->AddObserver(this);
373 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
374 media::SoundsManager
* manager
= media::SoundsManager::Get();
375 manager
->Initialize(SOUND_SHUTDOWN
,
376 bundle
.GetRawDataResource(IDR_SOUND_SHUTDOWN_WAV
));
378 SOUND_SPOKEN_FEEDBACK_ENABLED
,
379 bundle
.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_ENABLED_WAV
));
381 SOUND_SPOKEN_FEEDBACK_DISABLED
,
382 bundle
.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_DISABLED_WAV
));
383 manager
->Initialize(SOUND_PASSTHROUGH
,
384 bundle
.GetRawDataResource(IDR_SOUND_PASSTHROUGH_WAV
));
385 manager
->Initialize(SOUND_EXIT_SCREEN
,
386 bundle
.GetRawDataResource(IDR_SOUND_EXIT_SCREEN_WAV
));
387 manager
->Initialize(SOUND_ENTER_SCREEN
,
388 bundle
.GetRawDataResource(IDR_SOUND_ENTER_SCREEN_WAV
));
391 AccessibilityManager::~AccessibilityManager() {
392 CHECK(this == g_accessibility_manager
);
393 AccessibilityStatusEventDetails
details(
394 ACCESSIBILITY_MANAGER_SHUTDOWN
,
396 ui::A11Y_NOTIFICATION_NONE
);
397 NotifyAccessibilityStatusChanged(details
);
398 input_method::InputMethodManager::Get()->RemoveObserver(this);
401 bool AccessibilityManager::ShouldShowAccessibilityMenu() {
402 // If any of the loaded profiles has an accessibility feature turned on - or
403 // enforced to always show the menu - we return true to show the menu.
404 std::vector
<Profile
*> profiles
=
405 g_browser_process
->profile_manager()->GetLoadedProfiles();
406 for (std::vector
<Profile
*>::iterator it
= profiles
.begin();
407 it
!= profiles
.end();
409 PrefService
* pref_service
= (*it
)->GetPrefs();
410 if (pref_service
->GetBoolean(prefs::kAccessibilityStickyKeysEnabled
) ||
411 pref_service
->GetBoolean(prefs::kAccessibilityLargeCursorEnabled
) ||
412 pref_service
->GetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled
) ||
413 pref_service
->GetBoolean(prefs::kAccessibilityHighContrastEnabled
) ||
414 pref_service
->GetBoolean(prefs::kAccessibilityAutoclickEnabled
) ||
415 pref_service
->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu
) ||
416 pref_service
->GetBoolean(prefs::kAccessibilityScreenMagnifierEnabled
) ||
417 pref_service
->GetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled
))
423 bool AccessibilityManager::ShouldEnableCursorCompositing() {
424 #if defined(OS_CHROMEOS)
427 PrefService
* pref_service
= profile_
->GetPrefs();
428 // Enable cursor compositing when one or more of the listed accessibility
429 // features are turned on.
430 if (pref_service
->GetBoolean(prefs::kAccessibilityLargeCursorEnabled
) ||
431 pref_service
->GetBoolean(prefs::kAccessibilityHighContrastEnabled
) ||
432 pref_service
->GetBoolean(prefs::kAccessibilityScreenMagnifierEnabled
))
438 void AccessibilityManager::EnableLargeCursor(bool enabled
) {
442 PrefService
* pref_service
= profile_
->GetPrefs();
443 pref_service
->SetBoolean(prefs::kAccessibilityLargeCursorEnabled
, enabled
);
444 pref_service
->CommitPendingWrite();
447 void AccessibilityManager::UpdateLargeCursorFromPref() {
452 profile_
->GetPrefs()->GetBoolean(prefs::kAccessibilityLargeCursorEnabled
);
454 if (large_cursor_enabled_
== enabled
)
457 large_cursor_enabled_
= enabled
;
459 AccessibilityStatusEventDetails
details(
460 ACCESSIBILITY_TOGGLE_LARGE_CURSOR
,
462 ui::A11Y_NOTIFICATION_NONE
);
464 NotifyAccessibilityStatusChanged(details
);
465 #if !defined(USE_ATHENA)
466 // crbug.com/408733 (and for all USE_ATHENA in this file)
469 // Large cursor is implemented only in ash.
470 ash::Shell::GetInstance()->cursor_manager()->SetCursorSet(
471 enabled
? ui::CURSOR_SET_LARGE
: ui::CURSOR_SET_NORMAL
);
474 #if defined(OS_CHROMEOS)
475 ash::Shell::GetInstance()->SetCursorCompositingEnabled(
476 ShouldEnableCursorCompositing());
479 #endif // !USE_ATHENA
482 bool AccessibilityManager::IsIncognitoAllowed() {
483 // Supervised users can't create incognito-mode windows.
484 return !(user_manager::UserManager::Get()->IsLoggedInAsSupervisedUser());
487 bool AccessibilityManager::IsLargeCursorEnabled() {
488 return large_cursor_enabled_
;
491 void AccessibilityManager::EnableStickyKeys(bool enabled
) {
494 PrefService
* pref_service
= profile_
->GetPrefs();
495 pref_service
->SetBoolean(prefs::kAccessibilityStickyKeysEnabled
, enabled
);
496 pref_service
->CommitPendingWrite();
499 bool AccessibilityManager::IsStickyKeysEnabled() {
500 return sticky_keys_enabled_
;
503 void AccessibilityManager::UpdateStickyKeysFromPref() {
508 profile_
->GetPrefs()->GetBoolean(prefs::kAccessibilityStickyKeysEnabled
);
510 if (sticky_keys_enabled_
== enabled
)
513 sticky_keys_enabled_
= enabled
;
514 #if defined(USE_ASH) && !defined(USE_ATHENA)
515 ash::Shell::GetInstance()->sticky_keys_controller()->Enable(enabled
);
519 void AccessibilityManager::EnableSpokenFeedback(
521 ui::AccessibilityNotificationVisibility notify
) {
524 #if !defined(USE_ATHENA)
525 ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(
526 enabled
? ash::UMA_STATUS_AREA_ENABLE_SPOKEN_FEEDBACK
527 : ash::UMA_STATUS_AREA_DISABLE_SPOKEN_FEEDBACK
);
530 spoken_feedback_notification_
= notify
;
532 PrefService
* pref_service
= profile_
->GetPrefs();
533 pref_service
->SetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled
, enabled
);
534 pref_service
->CommitPendingWrite();
536 spoken_feedback_notification_
= ui::A11Y_NOTIFICATION_NONE
;
539 void AccessibilityManager::UpdateSpokenFeedbackFromPref() {
543 const bool enabled
= profile_
->GetPrefs()->GetBoolean(
544 prefs::kAccessibilitySpokenFeedbackEnabled
);
546 if (spoken_feedback_enabled_
== enabled
)
549 spoken_feedback_enabled_
= enabled
;
551 ExtensionAccessibilityEventRouter::GetInstance()->
552 SetAccessibilityEnabled(enabled
);
554 AccessibilityStatusEventDetails
details(
555 ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK
,
557 spoken_feedback_notification_
);
559 NotifyAccessibilityStatusChanged(details
);
566 UpdateBrailleImeState();
569 void AccessibilityManager::LoadChromeVox() {
570 base::Closure done_cb
= base::Bind(&AccessibilityManager::PostLoadChromeVox
,
571 weak_ptr_factory_
.GetWeakPtr(),
573 ScreenLocker
* screen_locker
= ScreenLocker::default_screen_locker();
574 if (screen_locker
&& screen_locker
->locked()) {
575 // If on the lock screen, loads ChromeVox only to the lock screen as for
576 // now. On unlock, it will be loaded to the user screen.
577 // (see. AccessibilityManager::Observe())
578 LoadChromeVoxToLockScreen(done_cb
);
580 LoadChromeVoxToUserScreen(done_cb
);
584 void AccessibilityManager::LoadChromeVoxToUserScreen(
585 const base::Closure
& done_cb
) {
586 if (chrome_vox_loaded_on_user_screen_
)
589 // Determine whether an OOBE screen is currently being shown. If so,
590 // ChromeVox will be injected directly into that screen.
591 content::WebUI
* login_web_ui
= NULL
;
593 if (ProfileHelper::IsSigninProfile(profile_
)) {
594 LoginDisplayHost
* login_display_host
= LoginDisplayHostImpl::default_host();
595 if (login_display_host
) {
596 WebUILoginView
* web_ui_login_view
=
597 login_display_host
->GetWebUILoginView();
598 if (web_ui_login_view
)
599 login_web_ui
= web_ui_login_view
->GetWebUI();
602 // Lock screen uses the signin progile.
603 chrome_vox_loaded_on_lock_screen_
= true;
606 chrome_vox_loaded_on_user_screen_
= true;
607 LoadChromeVoxExtension(
608 profile_
, login_web_ui
?
609 login_web_ui
->GetWebContents()->GetRenderViewHost() : NULL
,
613 void AccessibilityManager::LoadChromeVoxToLockScreen(
614 const base::Closure
& done_cb
) {
615 if (chrome_vox_loaded_on_lock_screen_
)
618 ScreenLocker
* screen_locker
= ScreenLocker::default_screen_locker();
619 if (screen_locker
&& screen_locker
->locked()) {
620 content::WebUI
* lock_web_ui
= screen_locker
->GetAssociatedWebUI();
622 Profile
* profile
= Profile::FromWebUI(lock_web_ui
);
623 chrome_vox_loaded_on_lock_screen_
= true;
624 LoadChromeVoxExtension(
626 lock_web_ui
->GetWebContents()->GetRenderViewHost(),
632 void AccessibilityManager::UnloadChromeVox() {
633 if (chrome_vox_loaded_on_lock_screen_
)
634 UnloadChromeVoxFromLockScreen();
636 if (chrome_vox_loaded_on_user_screen_
) {
637 UnloadChromeVoxExtension(profile_
);
638 chrome_vox_loaded_on_user_screen_
= false;
641 PostUnloadChromeVox(profile_
);
644 void AccessibilityManager::UnloadChromeVoxFromLockScreen() {
645 // Lock screen uses the signin progile.
646 Profile
* signin_profile
= ProfileHelper::GetSigninProfile();
647 UnloadChromeVoxExtension(signin_profile
);
648 chrome_vox_loaded_on_lock_screen_
= false;
651 bool AccessibilityManager::IsSpokenFeedbackEnabled() {
652 return spoken_feedback_enabled_
;
655 void AccessibilityManager::ToggleSpokenFeedback(
656 ui::AccessibilityNotificationVisibility notify
) {
657 EnableSpokenFeedback(!IsSpokenFeedbackEnabled(), notify
);
660 void AccessibilityManager::EnableHighContrast(bool enabled
) {
664 PrefService
* pref_service
= profile_
->GetPrefs();
665 pref_service
->SetBoolean(prefs::kAccessibilityHighContrastEnabled
, enabled
);
666 pref_service
->CommitPendingWrite();
669 void AccessibilityManager::UpdateHighContrastFromPref() {
673 const bool enabled
= profile_
->GetPrefs()->GetBoolean(
674 prefs::kAccessibilityHighContrastEnabled
);
676 if (high_contrast_enabled_
== enabled
)
679 high_contrast_enabled_
= enabled
;
681 AccessibilityStatusEventDetails
details(
682 ACCESSIBILITY_TOGGLE_HIGH_CONTRAST_MODE
,
684 ui::A11Y_NOTIFICATION_NONE
);
686 NotifyAccessibilityStatusChanged(details
);
688 #if !defined(USE_ATHENA)
691 ash::Shell::GetInstance()->high_contrast_controller()->SetEnabled(enabled
);
694 #if defined(OS_CHROMEOS)
695 ash::Shell::GetInstance()->SetCursorCompositingEnabled(
696 ShouldEnableCursorCompositing());
702 void AccessibilityManager::OnLocaleChanged() {
706 if (!IsSpokenFeedbackEnabled())
709 // If the system locale changes and spoken feedback is enabled,
710 // reload ChromeVox so that it switches its internal translations
711 // to the new language.
712 EnableSpokenFeedback(false, ui::A11Y_NOTIFICATION_NONE
);
713 EnableSpokenFeedback(true, ui::A11Y_NOTIFICATION_NONE
);
716 void AccessibilityManager::PlayEarcon(int sound_key
) {
717 #if !defined(USE_ATHENA)
718 DCHECK(sound_key
< chromeos::SOUND_COUNT
);
719 ash::PlaySystemSoundIfSpokenFeedback(sound_key
);
723 bool AccessibilityManager::IsHighContrastEnabled() {
724 return high_contrast_enabled_
;
727 void AccessibilityManager::EnableAutoclick(bool enabled
) {
731 PrefService
* pref_service
= profile_
->GetPrefs();
732 pref_service
->SetBoolean(prefs::kAccessibilityAutoclickEnabled
, enabled
);
733 pref_service
->CommitPendingWrite();
736 bool AccessibilityManager::IsAutoclickEnabled() {
737 return autoclick_enabled_
;
740 void AccessibilityManager::UpdateAutoclickFromPref() {
742 profile_
->GetPrefs()->GetBoolean(prefs::kAccessibilityAutoclickEnabled
);
744 if (autoclick_enabled_
== enabled
)
746 autoclick_enabled_
= enabled
;
748 #if defined(USE_ASH) && !defined(USE_ATHENA)
749 ash::Shell::GetInstance()->autoclick_controller()->SetEnabled(enabled
);
753 void AccessibilityManager::SetAutoclickDelay(int delay_ms
) {
757 PrefService
* pref_service
= profile_
->GetPrefs();
758 pref_service
->SetInteger(prefs::kAccessibilityAutoclickDelayMs
, delay_ms
);
759 pref_service
->CommitPendingWrite();
762 int AccessibilityManager::GetAutoclickDelay() const {
763 return autoclick_delay_ms_
;
766 void AccessibilityManager::UpdateAutoclickDelayFromPref() {
767 int autoclick_delay_ms
=
768 profile_
->GetPrefs()->GetInteger(prefs::kAccessibilityAutoclickDelayMs
);
770 if (autoclick_delay_ms
== autoclick_delay_ms_
)
772 autoclick_delay_ms_
= autoclick_delay_ms
;
774 #if defined(USE_ASH) && !defined(USE_ATHENA)
775 ash::Shell::GetInstance()->autoclick_controller()->SetAutoclickDelay(
776 autoclick_delay_ms_
);
780 void AccessibilityManager::EnableVirtualKeyboard(bool enabled
) {
784 PrefService
* pref_service
= profile_
->GetPrefs();
785 pref_service
->SetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled
,
787 pref_service
->CommitPendingWrite();
790 bool AccessibilityManager::IsVirtualKeyboardEnabled() {
791 return virtual_keyboard_enabled_
;
794 void AccessibilityManager::UpdateVirtualKeyboardFromPref() {
798 const bool enabled
= profile_
->GetPrefs()->GetBoolean(
799 prefs::kAccessibilityVirtualKeyboardEnabled
);
801 if (virtual_keyboard_enabled_
== enabled
)
803 virtual_keyboard_enabled_
= enabled
;
805 AccessibilityStatusEventDetails
details(
806 ACCESSIBILITY_TOGGLE_VIRTUAL_KEYBOARD
,
808 ui::A11Y_NOTIFICATION_NONE
);
810 NotifyAccessibilityStatusChanged(details
);
812 #if defined(USE_ASH) && !defined(USE_ATHENA)
813 keyboard::SetAccessibilityKeyboardEnabled(enabled
);
814 // Note that there are two versions of the on-screen keyboard. A full layout
815 // is provided for accessibility, which includes sticky modifier keys to
816 // enable typing of hotkeys. A compact version is used in touchview mode
817 // to provide a layout with larger keys to facilitate touch typing. In the
818 // event that the a11y keyboard is being disabled, an on-screen keyboard might
819 // still be enabled and a forced reset is required to pick up the layout
821 if (keyboard::IsKeyboardEnabled())
822 ash::Shell::GetInstance()->CreateKeyboard();
824 ash::Shell::GetInstance()->DeactivateKeyboard();
828 bool AccessibilityManager::IsBrailleDisplayConnected() const {
829 return braille_display_connected_
;
832 void AccessibilityManager::CheckBrailleState() {
833 BrailleController
* braille_controller
= GetBrailleController();
834 if (!scoped_braille_observer_
.IsObserving(braille_controller
))
835 scoped_braille_observer_
.Add(braille_controller
);
836 BrowserThread::PostTaskAndReplyWithResult(
839 base::Bind(&BrailleController::GetDisplayState
,
840 base::Unretained(braille_controller
)),
841 base::Bind(&AccessibilityManager::ReceiveBrailleDisplayState
,
842 weak_ptr_factory_
.GetWeakPtr()));
845 void AccessibilityManager::ReceiveBrailleDisplayState(
846 scoped_ptr
<extensions::api::braille_display_private::DisplayState
> state
) {
847 OnBrailleDisplayStateChanged(*state
);
850 void AccessibilityManager::UpdateBrailleImeState() {
853 PrefService
* pref_service
= profile_
->GetPrefs();
854 std::vector
<std::string
> preload_engines
;
855 base::SplitString(pref_service
->GetString(prefs::kLanguagePreloadEngines
),
858 std::vector
<std::string
>::iterator it
=
859 std::find(preload_engines
.begin(),
860 preload_engines
.end(),
861 extension_misc::kBrailleImeEngineId
);
862 bool is_enabled
= (it
!= preload_engines
.end());
863 bool should_be_enabled
=
864 (spoken_feedback_enabled_
&& braille_display_connected_
);
865 if (is_enabled
== should_be_enabled
)
867 if (should_be_enabled
)
868 preload_engines
.push_back(extension_misc::kBrailleImeEngineId
);
870 preload_engines
.erase(it
);
871 pref_service
->SetString(prefs::kLanguagePreloadEngines
,
872 JoinString(preload_engines
, ','));
873 braille_ime_current_
= false;
876 // Overridden from InputMethodManager::Observer.
877 void AccessibilityManager::InputMethodChanged(
878 input_method::InputMethodManager
* manager
,
880 #if defined(USE_ASH) && !defined(USE_ATHENA)
881 // Sticky keys is implemented only in ash.
882 // TODO(dpolukhin): support Athena, crbug.com/408733.
883 ash::Shell::GetInstance()->sticky_keys_controller()->SetModifiersEnabled(
884 manager
->IsISOLevel5ShiftUsedByCurrentInputMethod(),
885 manager
->IsAltGrUsedByCurrentInputMethod());
887 const chromeos::input_method::InputMethodDescriptor descriptor
=
888 manager
->GetActiveIMEState()->GetCurrentInputMethod();
889 braille_ime_current_
=
890 (descriptor
.id() == extension_misc::kBrailleImeEngineId
);
893 void AccessibilityManager::SetProfile(Profile
* profile
) {
894 pref_change_registrar_
.reset();
895 local_state_pref_change_registrar_
.reset();
898 // TODO(yoshiki): Move following code to PrefHandler.
899 pref_change_registrar_
.reset(new PrefChangeRegistrar
);
900 pref_change_registrar_
->Init(profile
->GetPrefs());
901 pref_change_registrar_
->Add(
902 prefs::kAccessibilityLargeCursorEnabled
,
903 base::Bind(&AccessibilityManager::UpdateLargeCursorFromPref
,
904 base::Unretained(this)));
905 pref_change_registrar_
->Add(
906 prefs::kAccessibilityStickyKeysEnabled
,
907 base::Bind(&AccessibilityManager::UpdateStickyKeysFromPref
,
908 base::Unretained(this)));
909 pref_change_registrar_
->Add(
910 prefs::kAccessibilitySpokenFeedbackEnabled
,
911 base::Bind(&AccessibilityManager::UpdateSpokenFeedbackFromPref
,
912 base::Unretained(this)));
913 pref_change_registrar_
->Add(
914 prefs::kAccessibilityHighContrastEnabled
,
915 base::Bind(&AccessibilityManager::UpdateHighContrastFromPref
,
916 base::Unretained(this)));
917 pref_change_registrar_
->Add(
918 prefs::kAccessibilityAutoclickEnabled
,
919 base::Bind(&AccessibilityManager::UpdateAutoclickFromPref
,
920 base::Unretained(this)));
921 pref_change_registrar_
->Add(
922 prefs::kAccessibilityAutoclickDelayMs
,
923 base::Bind(&AccessibilityManager::UpdateAutoclickDelayFromPref
,
924 base::Unretained(this)));
925 pref_change_registrar_
->Add(
926 prefs::kAccessibilityVirtualKeyboardEnabled
,
927 base::Bind(&AccessibilityManager::UpdateVirtualKeyboardFromPref
,
928 base::Unretained(this)));
930 local_state_pref_change_registrar_
.reset(new PrefChangeRegistrar
);
931 local_state_pref_change_registrar_
->Init(g_browser_process
->local_state());
932 local_state_pref_change_registrar_
->Add(
933 prefs::kApplicationLocale
,
934 base::Bind(&AccessibilityManager::OnLocaleChanged
,
935 base::Unretained(this)));
937 content::BrowserAccessibilityState::GetInstance()->AddHistogramCallback(
939 &AccessibilityManager::UpdateChromeOSAccessibilityHistograms
,
940 base::Unretained(this)));
943 large_cursor_pref_handler_
.HandleProfileChanged(profile_
, profile
);
944 spoken_feedback_pref_handler_
.HandleProfileChanged(profile_
, profile
);
945 high_contrast_pref_handler_
.HandleProfileChanged(profile_
, profile
);
946 autoclick_pref_handler_
.HandleProfileChanged(profile_
, profile
);
947 autoclick_delay_pref_handler_
.HandleProfileChanged(profile_
, profile
);
948 virtual_keyboard_pref_handler_
.HandleProfileChanged(profile_
, profile
);
950 bool had_profile
= (profile_
!= NULL
);
953 if (!had_profile
&& profile
)
956 UpdateBrailleImeState();
957 UpdateLargeCursorFromPref();
958 UpdateStickyKeysFromPref();
959 UpdateSpokenFeedbackFromPref();
960 UpdateHighContrastFromPref();
961 UpdateAutoclickFromPref();
962 UpdateAutoclickDelayFromPref();
963 UpdateVirtualKeyboardFromPref();
966 #if !defined(USE_ATHENA)
967 void AccessibilityManager::ActiveUserChanged(const std::string
& user_id
) {
968 SetProfile(ProfileManager::GetActiveUserProfile());
972 void AccessibilityManager::SetProfileForTest(Profile
* profile
) {
976 void AccessibilityManager::SetBrailleControllerForTest(
977 BrailleController
* controller
) {
978 g_braille_controller_for_test
= controller
;
981 void AccessibilityManager::EnableSystemSounds(bool system_sounds_enabled
) {
982 system_sounds_enabled_
= system_sounds_enabled
;
985 base::TimeDelta
AccessibilityManager::PlayShutdownSound() {
986 if (!system_sounds_enabled_
)
987 return base::TimeDelta();
988 system_sounds_enabled_
= false;
989 #if !defined(USE_ATHENA)
990 if (!ash::PlaySystemSoundIfSpokenFeedback(SOUND_SHUTDOWN
))
991 return base::TimeDelta();
993 return media::SoundsManager::Get()->GetDuration(SOUND_SHUTDOWN
);
996 void AccessibilityManager::InjectChromeVox(RenderViewHost
* render_view_host
) {
997 LoadChromeVoxExtension(profile_
, render_view_host
, base::Closure());
1000 scoped_ptr
<AccessibilityStatusSubscription
>
1001 AccessibilityManager::RegisterCallback(
1002 const AccessibilityStatusCallback
& cb
) {
1003 return callback_list_
.Add(cb
);
1006 void AccessibilityManager::NotifyAccessibilityStatusChanged(
1007 AccessibilityStatusEventDetails
& details
) {
1008 callback_list_
.Notify(details
);
1011 void AccessibilityManager::UpdateChromeOSAccessibilityHistograms() {
1012 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosSpokenFeedback",
1013 IsSpokenFeedbackEnabled());
1014 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosHighContrast",
1015 IsHighContrastEnabled());
1016 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosVirtualKeyboard",
1017 IsVirtualKeyboardEnabled());
1018 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosStickyKeys", IsStickyKeysEnabled());
1019 #if !defined(USE_ATHENA)
1020 if (MagnificationManager::Get()) {
1021 uint32 type
= MagnificationManager::Get()->IsMagnifierEnabled() ?
1022 MagnificationManager::Get()->GetMagnifierType() : 0;
1023 // '0' means magnifier is disabled.
1024 UMA_HISTOGRAM_ENUMERATION("Accessibility.CrosScreenMagnifier",
1026 ui::kMaxMagnifierType
+ 1);
1030 const PrefService
* const prefs
= profile_
->GetPrefs();
1031 UMA_HISTOGRAM_BOOLEAN(
1032 "Accessibility.CrosLargeCursor",
1033 prefs
->GetBoolean(prefs::kAccessibilityLargeCursorEnabled
));
1034 UMA_HISTOGRAM_BOOLEAN(
1035 "Accessibility.CrosAlwaysShowA11yMenu",
1036 prefs
->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu
));
1038 bool autoclick_enabled
=
1039 prefs
->GetBoolean(prefs::kAccessibilityAutoclickEnabled
);
1040 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosAutoclick", autoclick_enabled
);
1041 if (autoclick_enabled
) {
1042 // We only want to log the autoclick delay if the user has actually
1043 // enabled autoclick.
1044 UMA_HISTOGRAM_CUSTOM_TIMES(
1045 "Accessibility.CrosAutoclickDelay",
1046 base::TimeDelta::FromMilliseconds(
1047 prefs
->GetInteger(prefs::kAccessibilityAutoclickDelayMs
)),
1048 base::TimeDelta::FromMilliseconds(1),
1049 base::TimeDelta::FromMilliseconds(3000),
1055 void AccessibilityManager::Observe(
1057 const content::NotificationSource
& source
,
1058 const content::NotificationDetails
& details
) {
1060 case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE
: {
1061 // Update |profile_| when entering the login screen.
1062 Profile
* profile
= ProfileManager::GetActiveUserProfile();
1063 if (ProfileHelper::IsSigninProfile(profile
))
1064 SetProfile(profile
);
1067 case chrome::NOTIFICATION_SESSION_STARTED
:
1068 // Update |profile_| when entering a session.
1069 SetProfile(ProfileManager::GetActiveUserProfile());
1071 // Ensure ChromeVox makes announcements at the start of new sessions.
1072 should_speak_chrome_vox_announcements_on_user_screen_
= true;
1074 #if !defined(USE_ATHENA)
1075 // Add a session state observer to be able to monitor session changes.
1076 if (!session_state_observer_
.get() && ash::Shell::HasInstance())
1077 session_state_observer_
.reset(
1078 new ash::ScopedSessionStateObserver(this));
1081 case chrome::NOTIFICATION_PROFILE_DESTROYED
: {
1082 // Update |profile_| when exiting a session or shutting down.
1083 Profile
* profile
= content::Source
<Profile
>(source
).ptr();
1084 if (profile_
== profile
)
1088 case chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED
: {
1089 bool is_screen_locked
= *content::Details
<bool>(details
).ptr();
1090 if (spoken_feedback_enabled_
) {
1091 if (is_screen_locked
)
1092 LoadChromeVoxToLockScreen(base::Closure());
1093 // If spoken feedback was enabled, make sure it is also enabled on
1095 // The status tray gets verbalized by user screen ChromeVox, so we need
1096 // to load it on the user screen even if the screen is locked.
1097 LoadChromeVoxToUserScreen(base::Closure());
1104 void AccessibilityManager::OnBrailleDisplayStateChanged(
1105 const DisplayState
& display_state
) {
1106 braille_display_connected_
= display_state
.available
;
1107 if (braille_display_connected_
) {
1108 EnableSpokenFeedback(true, ui::A11Y_NOTIFICATION_SHOW
);
1110 UpdateBrailleImeState();
1112 AccessibilityStatusEventDetails
details(
1113 ACCESSIBILITY_BRAILLE_DISPLAY_CONNECTION_STATE_CHANGED
,
1114 braille_display_connected_
,
1115 ui::A11Y_NOTIFICATION_SHOW
);
1116 NotifyAccessibilityStatusChanged(details
);
1119 void AccessibilityManager::OnBrailleKeyEvent(const KeyEvent
& event
) {
1120 // Ensure the braille IME is active on braille keyboard (dots) input.
1121 if ((event
.command
==
1122 extensions::api::braille_display_private::KEY_COMMAND_DOTS
) &&
1123 !braille_ime_current_
) {
1124 input_method::InputMethodManager::Get()
1125 ->GetActiveIMEState()
1126 ->ChangeInputMethod(extension_misc::kBrailleImeEngineId
,
1127 false /* show_message */);
1131 void AccessibilityManager::PostLoadChromeVox(Profile
* profile
) {
1132 #if !defined(USE_ATHENA)
1133 // Do any setup work needed immediately after ChromeVox actually loads.
1134 if (system_sounds_enabled_
)
1135 ash::PlaySystemSoundAlways(SOUND_SPOKEN_FEEDBACK_ENABLED
);
1138 ExtensionAccessibilityEventRouter::GetInstance()->
1139 OnChromeVoxLoadStateChanged(profile_
,
1140 IsSpokenFeedbackEnabled(),
1141 chrome_vox_loaded_on_lock_screen_
||
1142 should_speak_chrome_vox_announcements_on_user_screen_
);
1144 should_speak_chrome_vox_announcements_on_user_screen_
=
1145 chrome_vox_loaded_on_lock_screen_
;
1148 void AccessibilityManager::PostUnloadChromeVox(Profile
* profile
) {
1149 #if !defined(USE_ATHENA)
1150 // Do any teardown work needed immediately after ChromeVox actually unloads.
1151 if (system_sounds_enabled_
)
1152 ash::PlaySystemSoundAlways(SOUND_SPOKEN_FEEDBACK_DISABLED
);
1154 // Clear the accessibility focus ring.
1155 AccessibilityFocusRingController::GetInstance()->SetFocusRing(
1156 std::vector
<gfx::Rect
>());
1159 } // namespace chromeos