1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h" 6 7 #include "ash/audio/sounds.h" 8 #include "ash/autoclick/autoclick_controller.h" 9 #include "ash/high_contrast/high_contrast_controller.h" 10 #include "ash/metrics/user_metrics_recorder.h" 11 #include "ash/session/session_state_delegate.h" 12 #include "ash/shell.h" 13 #include "ash/sticky_keys/sticky_keys_controller.h" 14 #include "ash/system/tray/system_tray_notifier.h" 15 #include "base/callback.h" 16 #include "base/callback_helpers.h" 17 #include "base/memory/scoped_ptr.h" 18 #include "base/memory/singleton.h" 19 #include "base/metrics/histogram.h" 20 #include "base/path_service.h" 21 #include "base/prefs/pref_member.h" 22 #include "base/prefs/pref_service.h" 23 #include "base/strings/string_split.h" 24 #include "base/strings/string_util.h" 25 #include "base/time/time.h" 26 #include "base/values.h" 27 #include "chrome/browser/accessibility/accessibility_extension_api.h" 28 #include "chrome/browser/browser_process.h" 29 #include "chrome/browser/chrome_notification_types.h" 30 #include "chrome/browser/chromeos/accessibility/magnification_manager.h" 31 #include "chrome/browser/chromeos/login/lock/screen_locker.h" 32 #include "chrome/browser/chromeos/login/ui/login_display_host.h" 33 #include "chrome/browser/chromeos/login/ui/login_display_host_impl.h" 34 #include "chrome/browser/chromeos/login/ui/webui_login_view.h" 35 #include "chrome/browser/chromeos/profiles/profile_helper.h" 36 #include "chrome/browser/chromeos/ui/accessibility_focus_ring_controller.h" 37 #include "chrome/browser/extensions/component_loader.h" 38 #include "chrome/browser/extensions/extension_service.h" 39 #include "chrome/browser/profiles/profile.h" 40 #include "chrome/browser/profiles/profile_manager.h" 41 #include "chrome/common/chrome_paths.h" 42 #include "chrome/common/extensions/api/accessibility_private.h" 43 #include "chrome/common/extensions/extension_constants.h" 44 #include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h" 45 #include "chrome/common/pref_names.h" 46 #include "chrome/grit/browser_resources.h" 47 #include "chromeos/audio/chromeos_sounds.h" 48 #include "chromeos/ime/input_method_manager.h" 49 #include "chromeos/login/login_state.h" 50 #include "components/user_manager/user_manager.h" 51 #include "content/public/browser/browser_accessibility_state.h" 52 #include "content/public/browser/browser_thread.h" 53 #include "content/public/browser/notification_details.h" 54 #include "content/public/browser/notification_service.h" 55 #include "content/public/browser/notification_source.h" 56 #include "content/public/browser/render_process_host.h" 57 #include "content/public/browser/render_view_host.h" 58 #include "content/public/browser/web_contents.h" 59 #include "content/public/browser/web_ui.h" 60 #include "extensions/browser/extension_system.h" 61 #include "extensions/browser/file_reader.h" 62 #include "extensions/common/extension.h" 63 #include "extensions/common/extension_messages.h" 64 #include "extensions/common/extension_resource.h" 65 #include "media/audio/sounds/sounds_manager.h" 66 #include "ui/base/resource/resource_bundle.h" 67 #include "ui/keyboard/keyboard_controller.h" 68 #include "ui/keyboard/keyboard_util.h" 69 70 using content::BrowserThread; 71 using content::RenderViewHost; 72 using extensions::api::braille_display_private::BrailleController; 73 using extensions::api::braille_display_private::DisplayState; 74 using extensions::api::braille_display_private::KeyEvent; 75 76 namespace chromeos { 77 78 namespace { 79 80 static chromeos::AccessibilityManager* g_accessibility_manager = NULL; 81 82 static BrailleController* g_braille_controller_for_test = NULL; 83 84 BrailleController* GetBrailleController() { 85 return g_braille_controller_for_test 86 ? g_braille_controller_for_test 87 : BrailleController::GetInstance(); 88 } 89 90 base::FilePath GetChromeVoxPath() { 91 base::FilePath path; 92 if (!PathService::Get(chrome::DIR_RESOURCES, &path)) 93 NOTREACHED(); 94 path = path.Append(extension_misc::kChromeVoxExtensionPath); 95 return path; 96 } 97 98 // Helper class that directly loads an extension's content scripts into 99 // all of the frames corresponding to a given RenderViewHost. 100 class ContentScriptLoader { 101 public: 102 // Initialize the ContentScriptLoader with the ID of the extension 103 // and the RenderViewHost where the scripts should be loaded. 104 ContentScriptLoader(const std::string& extension_id, 105 int render_process_id, 106 int render_view_id) 107 : extension_id_(extension_id), 108 render_process_id_(render_process_id), 109 render_view_id_(render_view_id) {} 110 111 // Call this once with the ExtensionResource corresponding to each 112 // content script to be loaded. 113 void AppendScript(extensions::ExtensionResource resource) { 114 resources_.push(resource); 115 } 116 117 // Finally, call this method once to fetch all of the resources and 118 // load them. This method will delete this object when done. 119 void Run() { 120 if (resources_.empty()) { 121 delete this; 122 return; 123 } 124 125 extensions::ExtensionResource resource = resources_.front(); 126 resources_.pop(); 127 scoped_refptr<FileReader> reader(new FileReader(resource, base::Bind( 128 &ContentScriptLoader::OnFileLoaded, base::Unretained(this)))); 129 reader->Start(); 130 } 131 132 private: 133 void OnFileLoaded(bool success, const std::string& data) { 134 if (success) { 135 ExtensionMsg_ExecuteCode_Params params; 136 params.request_id = 0; 137 params.extension_id = extension_id_; 138 params.is_javascript = true; 139 params.code = data; 140 params.run_at = extensions::UserScript::DOCUMENT_IDLE; 141 params.all_frames = true; 142 params.match_about_blank = false; 143 params.in_main_world = false; 144 145 RenderViewHost* render_view_host = 146 RenderViewHost::FromID(render_process_id_, render_view_id_); 147 if (render_view_host) { 148 render_view_host->Send(new ExtensionMsg_ExecuteCode( 149 render_view_host->GetRoutingID(), params)); 150 } 151 } 152 Run(); 153 } 154 155 std::string extension_id_; 156 int render_process_id_; 157 int render_view_id_; 158 std::queue<extensions::ExtensionResource> resources_; 159 }; 160 161 void InjectChromeVoxContentScript( 162 ExtensionService* extension_service, 163 int render_process_id, 164 int render_view_id, 165 const base::Closure& done_cb); 166 167 void LoadChromeVoxExtension( 168 Profile* profile, 169 RenderViewHost* render_view_host, 170 base::Closure done_cb) { 171 ExtensionService* extension_service = 172 extensions::ExtensionSystem::Get(profile)->extension_service(); 173 if (render_view_host) { 174 // Wrap the passed in callback to inject the content script. 175 done_cb = base::Bind( 176 &InjectChromeVoxContentScript, 177 extension_service, 178 render_view_host->GetProcess()->GetID(), 179 render_view_host->GetRoutingID(), 180 done_cb); 181 } 182 extension_service->component_loader()->AddChromeVoxExtension(done_cb); 183 } 184 185 void InjectChromeVoxContentScript( 186 ExtensionService* extension_service, 187 int render_process_id, 188 int render_view_id, 189 const base::Closure& done_cb) { 190 // Make sure to always run |done_cb|. ChromeVox was loaded even if we end up 191 // not injecting into this particular render view. 192 base::ScopedClosureRunner done_runner(done_cb); 193 RenderViewHost* render_view_host = 194 RenderViewHost::FromID(render_process_id, render_view_id); 195 if (!render_view_host) 196 return; 197 const extensions::Extension* extension = 198 extension_service->extensions()->GetByID( 199 extension_misc::kChromeVoxExtensionId); 200 201 // Set a flag to tell ChromeVox that it's just been enabled, 202 // so that it won't interrupt our speech feedback enabled message. 203 ExtensionMsg_ExecuteCode_Params params; 204 params.request_id = 0; 205 params.extension_id = extension->id(); 206 params.is_javascript = true; 207 params.code = "window.INJECTED_AFTER_LOAD = true;"; 208 params.run_at = extensions::UserScript::DOCUMENT_IDLE; 209 params.all_frames = true; 210 params.match_about_blank = false; 211 params.in_main_world = false; 212 render_view_host->Send(new ExtensionMsg_ExecuteCode( 213 render_view_host->GetRoutingID(), params)); 214 215 // Inject ChromeVox' content scripts. 216 ContentScriptLoader* loader = new ContentScriptLoader( 217 extension->id(), render_view_host->GetProcess()->GetID(), 218 render_view_host->GetRoutingID()); 219 220 const extensions::UserScriptList& content_scripts = 221 extensions::ContentScriptsInfo::GetContentScripts(extension); 222 for (size_t i = 0; i < content_scripts.size(); i++) { 223 const extensions::UserScript& script = content_scripts[i]; 224 for (size_t j = 0; j < script.js_scripts().size(); ++j) { 225 const extensions::UserScript::File& file = script.js_scripts()[j]; 226 extensions::ExtensionResource resource = extension->GetResource( 227 file.relative_path()); 228 loader->AppendScript(resource); 229 } 230 } 231 loader->Run(); // It cleans itself up when done. 232 } 233 234 void UnloadChromeVoxExtension(Profile* profile) { 235 base::FilePath path = GetChromeVoxPath(); 236 ExtensionService* extension_service = 237 extensions::ExtensionSystem::Get(profile)->extension_service(); 238 extension_service->component_loader()->Remove(path); 239 } 240 241 } // namespace 242 243 /////////////////////////////////////////////////////////////////////////////// 244 // AccessibilityStatusEventDetails 245 246 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails( 247 AccessibilityNotificationType notification_type, 248 bool enabled, 249 ash::AccessibilityNotificationVisibility notify) 250 : notification_type(notification_type), 251 enabled(enabled), 252 magnifier_type(ash::kDefaultMagnifierType), 253 notify(notify) {} 254 255 AccessibilityStatusEventDetails::AccessibilityStatusEventDetails( 256 AccessibilityNotificationType notification_type, 257 bool enabled, 258 ash::MagnifierType magnifier_type, 259 ash::AccessibilityNotificationVisibility notify) 260 : notification_type(notification_type), 261 enabled(enabled), 262 magnifier_type(magnifier_type), 263 notify(notify) {} 264 265 /////////////////////////////////////////////////////////////////////////////// 266 // 267 // AccessibilityManager::PrefHandler 268 269 AccessibilityManager::PrefHandler::PrefHandler(const char* pref_path) 270 : pref_path_(pref_path) {} 271 272 AccessibilityManager::PrefHandler::~PrefHandler() {} 273 274 void AccessibilityManager::PrefHandler::HandleProfileChanged( 275 Profile* previous_profile, Profile* current_profile) { 276 // Returns if the current profile is null. 277 if (!current_profile) 278 return; 279 280 // If the user set a pref value on the login screen and is now starting a 281 // session with a new profile, copy the pref value to the profile. 282 if ((previous_profile && 283 ProfileHelper::IsSigninProfile(previous_profile) && 284 current_profile->IsNewProfile() && 285 !ProfileHelper::IsSigninProfile(current_profile)) || 286 // Special case for Guest mode: 287 // Guest mode launches a guest-mode browser process before session starts, 288 // so the previous profile is null. 289 (!previous_profile && 290 current_profile->IsGuestSession())) { 291 // Returns if the pref has not been set by the user. 292 const PrefService::Preference* pref = ProfileHelper::GetSigninProfile()-> 293 GetPrefs()->FindPreference(pref_path_); 294 if (!pref || !pref->IsUserControlled()) 295 return; 296 297 // Copy the pref value from the signin screen. 298 const base::Value* value_on_login = pref->GetValue(); 299 PrefService* user_prefs = current_profile->GetPrefs(); 300 user_prefs->Set(pref_path_, *value_on_login); 301 } 302 } 303 304 /////////////////////////////////////////////////////////////////////////////// 305 // 306 // AccessibilityManager 307 308 // static 309 void AccessibilityManager::Initialize() { 310 CHECK(g_accessibility_manager == NULL); 311 g_accessibility_manager = new AccessibilityManager(); 312 } 313 314 // static 315 void AccessibilityManager::Shutdown() { 316 CHECK(g_accessibility_manager); 317 delete g_accessibility_manager; 318 g_accessibility_manager = NULL; 319 } 320 321 // static 322 AccessibilityManager* AccessibilityManager::Get() { 323 return g_accessibility_manager; 324 } 325 326 AccessibilityManager::AccessibilityManager() 327 : profile_(NULL), 328 chrome_vox_loaded_on_lock_screen_(false), 329 chrome_vox_loaded_on_user_screen_(false), 330 large_cursor_pref_handler_(prefs::kAccessibilityLargeCursorEnabled), 331 spoken_feedback_pref_handler_(prefs::kAccessibilitySpokenFeedbackEnabled), 332 high_contrast_pref_handler_(prefs::kAccessibilityHighContrastEnabled), 333 autoclick_pref_handler_(prefs::kAccessibilityAutoclickEnabled), 334 autoclick_delay_pref_handler_(prefs::kAccessibilityAutoclickDelayMs), 335 virtual_keyboard_pref_handler_( 336 prefs::kAccessibilityVirtualKeyboardEnabled), 337 large_cursor_enabled_(false), 338 sticky_keys_enabled_(false), 339 spoken_feedback_enabled_(false), 340 high_contrast_enabled_(false), 341 autoclick_enabled_(false), 342 autoclick_delay_ms_(ash::AutoclickController::kDefaultAutoclickDelayMs), 343 virtual_keyboard_enabled_(false), 344 spoken_feedback_notification_(ash::A11Y_NOTIFICATION_NONE), 345 should_speak_chrome_vox_announcements_on_user_screen_(true), 346 system_sounds_enabled_(false), 347 braille_display_connected_(false), 348 scoped_braille_observer_(this), 349 braille_ime_current_(false), 350 weak_ptr_factory_(this) { 351 notification_registrar_.Add(this, 352 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE, 353 content::NotificationService::AllSources()); 354 notification_registrar_.Add(this, 355 chrome::NOTIFICATION_SESSION_STARTED, 356 content::NotificationService::AllSources()); 357 notification_registrar_.Add(this, 358 chrome::NOTIFICATION_PROFILE_DESTROYED, 359 content::NotificationService::AllSources()); 360 notification_registrar_.Add(this, 361 chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED, 362 content::NotificationService::AllSources()); 363 364 input_method::InputMethodManager::Get()->AddObserver(this); 365 366 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 367 media::SoundsManager* manager = media::SoundsManager::Get(); 368 manager->Initialize(SOUND_SHUTDOWN, 369 bundle.GetRawDataResource(IDR_SOUND_SHUTDOWN_WAV)); 370 manager->Initialize( 371 SOUND_SPOKEN_FEEDBACK_ENABLED, 372 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_ENABLED_WAV)); 373 manager->Initialize( 374 SOUND_SPOKEN_FEEDBACK_DISABLED, 375 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_DISABLED_WAV)); 376 manager->Initialize(SOUND_PASSTHROUGH, 377 bundle.GetRawDataResource(IDR_SOUND_PASSTHROUGH_WAV)); 378 manager->Initialize(SOUND_EXIT_SCREEN, 379 bundle.GetRawDataResource(IDR_SOUND_EXIT_SCREEN_WAV)); 380 manager->Initialize(SOUND_ENTER_SCREEN, 381 bundle.GetRawDataResource(IDR_SOUND_ENTER_SCREEN_WAV)); 382 } 383 384 AccessibilityManager::~AccessibilityManager() { 385 CHECK(this == g_accessibility_manager); 386 AccessibilityStatusEventDetails details( 387 ACCESSIBILITY_MANAGER_SHUTDOWN, 388 false, 389 ash::A11Y_NOTIFICATION_NONE); 390 NotifyAccessibilityStatusChanged(details); 391 input_method::InputMethodManager::Get()->RemoveObserver(this); 392 } 393 394 bool AccessibilityManager::ShouldShowAccessibilityMenu() { 395 // If any of the loaded profiles has an accessibility feature turned on - or 396 // enforced to always show the menu - we return true to show the menu. 397 std::vector<Profile*> profiles = 398 g_browser_process->profile_manager()->GetLoadedProfiles(); 399 for (std::vector<Profile*>::iterator it = profiles.begin(); 400 it != profiles.end(); 401 ++it) { 402 PrefService* pref_service = (*it)->GetPrefs(); 403 if (pref_service->GetBoolean(prefs::kAccessibilityStickyKeysEnabled) || 404 pref_service->GetBoolean(prefs::kAccessibilityLargeCursorEnabled) || 405 pref_service->GetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled) || 406 pref_service->GetBoolean(prefs::kAccessibilityHighContrastEnabled) || 407 pref_service->GetBoolean(prefs::kAccessibilityAutoclickEnabled) || 408 pref_service->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu) || 409 pref_service->GetBoolean(prefs::kAccessibilityScreenMagnifierEnabled) || 410 pref_service->GetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled)) 411 return true; 412 } 413 return false; 414 } 415 416 bool AccessibilityManager::ShouldEnableCursorCompositing() { 417 #if defined(OS_CHROMEOS) 418 if (!profile_) 419 return false; 420 PrefService* pref_service = profile_->GetPrefs(); 421 // Enable cursor compositing when one or more of the listed accessibility 422 // features are turned on. 423 if (pref_service->GetBoolean(prefs::kAccessibilityLargeCursorEnabled) || 424 pref_service->GetBoolean(prefs::kAccessibilityHighContrastEnabled) || 425 pref_service->GetBoolean(prefs::kAccessibilityScreenMagnifierEnabled)) 426 return true; 427 #endif 428 return false; 429 } 430 431 void AccessibilityManager::EnableLargeCursor(bool enabled) { 432 if (!profile_) 433 return; 434 435 PrefService* pref_service = profile_->GetPrefs(); 436 pref_service->SetBoolean(prefs::kAccessibilityLargeCursorEnabled, enabled); 437 pref_service->CommitPendingWrite(); 438 } 439 440 void AccessibilityManager::UpdateLargeCursorFromPref() { 441 if (!profile_) 442 return; 443 444 const bool enabled = 445 profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityLargeCursorEnabled); 446 447 if (large_cursor_enabled_ == enabled) 448 return; 449 450 large_cursor_enabled_ = enabled; 451 452 AccessibilityStatusEventDetails details( 453 ACCESSIBILITY_TOGGLE_LARGE_CURSOR, 454 enabled, 455 ash::A11Y_NOTIFICATION_NONE); 456 457 NotifyAccessibilityStatusChanged(details); 458 #if !defined(USE_ATHENA) 459 // crbug.com/408733 (and for all USE_ATHENA in this file) 460 461 #if defined(USE_ASH) 462 // Large cursor is implemented only in ash. 463 ash::Shell::GetInstance()->cursor_manager()->SetCursorSet( 464 enabled ? ui::CURSOR_SET_LARGE : ui::CURSOR_SET_NORMAL); 465 #endif 466 467 #if defined(OS_CHROMEOS) 468 ash::Shell::GetInstance()->SetCursorCompositingEnabled( 469 ShouldEnableCursorCompositing()); 470 #endif 471 472 #endif // !USE_ATHENA 473 } 474 475 bool AccessibilityManager::IsIncognitoAllowed() { 476 // Supervised users can't create incognito-mode windows. 477 return !(user_manager::UserManager::Get()->IsLoggedInAsSupervisedUser()); 478 } 479 480 bool AccessibilityManager::IsLargeCursorEnabled() { 481 return large_cursor_enabled_; 482 } 483 484 void AccessibilityManager::EnableStickyKeys(bool enabled) { 485 if (!profile_) 486 return; 487 PrefService* pref_service = profile_->GetPrefs(); 488 pref_service->SetBoolean(prefs::kAccessibilityStickyKeysEnabled, enabled); 489 pref_service->CommitPendingWrite(); 490 } 491 492 bool AccessibilityManager::IsStickyKeysEnabled() { 493 return sticky_keys_enabled_; 494 } 495 496 void AccessibilityManager::UpdateStickyKeysFromPref() { 497 if (!profile_) 498 return; 499 500 const bool enabled = 501 profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityStickyKeysEnabled); 502 503 if (sticky_keys_enabled_ == enabled) 504 return; 505 506 sticky_keys_enabled_ = enabled; 507 #if defined(USE_ASH) && !defined(USE_ATHENA) 508 ash::Shell::GetInstance()->sticky_keys_controller()->Enable(enabled); 509 #endif 510 } 511 512 void AccessibilityManager::EnableSpokenFeedback( 513 bool enabled, 514 ash::AccessibilityNotificationVisibility notify) { 515 if (!profile_) 516 return; 517 #if !defined(USE_ATHENA) 518 ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction( 519 enabled ? ash::UMA_STATUS_AREA_ENABLE_SPOKEN_FEEDBACK 520 : ash::UMA_STATUS_AREA_DISABLE_SPOKEN_FEEDBACK); 521 #endif 522 523 spoken_feedback_notification_ = notify; 524 525 PrefService* pref_service = profile_->GetPrefs(); 526 pref_service->SetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled, enabled); 527 pref_service->CommitPendingWrite(); 528 529 spoken_feedback_notification_ = ash::A11Y_NOTIFICATION_NONE; 530 } 531 532 void AccessibilityManager::UpdateSpokenFeedbackFromPref() { 533 if (!profile_) 534 return; 535 536 const bool enabled = profile_->GetPrefs()->GetBoolean( 537 prefs::kAccessibilitySpokenFeedbackEnabled); 538 539 if (spoken_feedback_enabled_ == enabled) 540 return; 541 542 spoken_feedback_enabled_ = enabled; 543 544 ExtensionAccessibilityEventRouter::GetInstance()-> 545 SetAccessibilityEnabled(enabled); 546 547 AccessibilityStatusEventDetails details( 548 ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK, 549 enabled, 550 spoken_feedback_notification_); 551 552 NotifyAccessibilityStatusChanged(details); 553 554 if (enabled) { 555 LoadChromeVox(); 556 } else { 557 UnloadChromeVox(); 558 } 559 UpdateBrailleImeState(); 560 } 561 562 void AccessibilityManager::LoadChromeVox() { 563 base::Closure done_cb = base::Bind(&AccessibilityManager::PostLoadChromeVox, 564 weak_ptr_factory_.GetWeakPtr(), 565 profile_); 566 ScreenLocker* screen_locker = ScreenLocker::default_screen_locker(); 567 if (screen_locker && screen_locker->locked()) { 568 // If on the lock screen, loads ChromeVox only to the lock screen as for 569 // now. On unlock, it will be loaded to the user screen. 570 // (see. AccessibilityManager::Observe()) 571 LoadChromeVoxToLockScreen(done_cb); 572 } else { 573 LoadChromeVoxToUserScreen(done_cb); 574 } 575 } 576 577 void AccessibilityManager::LoadChromeVoxToUserScreen( 578 const base::Closure& done_cb) { 579 if (chrome_vox_loaded_on_user_screen_) 580 return; 581 582 // Determine whether an OOBE screen is currently being shown. If so, 583 // ChromeVox will be injected directly into that screen. 584 content::WebUI* login_web_ui = NULL; 585 586 if (ProfileHelper::IsSigninProfile(profile_)) { 587 LoginDisplayHost* login_display_host = LoginDisplayHostImpl::default_host(); 588 if (login_display_host) { 589 WebUILoginView* web_ui_login_view = 590 login_display_host->GetWebUILoginView(); 591 if (web_ui_login_view) 592 login_web_ui = web_ui_login_view->GetWebUI(); 593 } 594 595 // Lock screen uses the signin progile. 596 chrome_vox_loaded_on_lock_screen_ = true; 597 } 598 599 chrome_vox_loaded_on_user_screen_ = true; 600 LoadChromeVoxExtension( 601 profile_, login_web_ui ? 602 login_web_ui->GetWebContents()->GetRenderViewHost() : NULL, 603 done_cb); 604 } 605 606 void AccessibilityManager::LoadChromeVoxToLockScreen( 607 const base::Closure& done_cb) { 608 if (chrome_vox_loaded_on_lock_screen_) 609 return; 610 611 ScreenLocker* screen_locker = ScreenLocker::default_screen_locker(); 612 if (screen_locker && screen_locker->locked()) { 613 content::WebUI* lock_web_ui = screen_locker->GetAssociatedWebUI(); 614 if (lock_web_ui) { 615 Profile* profile = Profile::FromWebUI(lock_web_ui); 616 chrome_vox_loaded_on_lock_screen_ = true; 617 LoadChromeVoxExtension( 618 profile, 619 lock_web_ui->GetWebContents()->GetRenderViewHost(), 620 done_cb); 621 } 622 } 623 } 624 625 void AccessibilityManager::UnloadChromeVox() { 626 if (chrome_vox_loaded_on_lock_screen_) 627 UnloadChromeVoxFromLockScreen(); 628 629 if (chrome_vox_loaded_on_user_screen_) { 630 UnloadChromeVoxExtension(profile_); 631 chrome_vox_loaded_on_user_screen_ = false; 632 } 633 634 PostUnloadChromeVox(profile_); 635 } 636 637 void AccessibilityManager::UnloadChromeVoxFromLockScreen() { 638 // Lock screen uses the signin progile. 639 Profile* signin_profile = ProfileHelper::GetSigninProfile(); 640 UnloadChromeVoxExtension(signin_profile); 641 chrome_vox_loaded_on_lock_screen_ = false; 642 } 643 644 bool AccessibilityManager::IsSpokenFeedbackEnabled() { 645 return spoken_feedback_enabled_; 646 } 647 648 void AccessibilityManager::ToggleSpokenFeedback( 649 ash::AccessibilityNotificationVisibility notify) { 650 EnableSpokenFeedback(!IsSpokenFeedbackEnabled(), notify); 651 } 652 653 void AccessibilityManager::EnableHighContrast(bool enabled) { 654 if (!profile_) 655 return; 656 657 PrefService* pref_service = profile_->GetPrefs(); 658 pref_service->SetBoolean(prefs::kAccessibilityHighContrastEnabled, enabled); 659 pref_service->CommitPendingWrite(); 660 } 661 662 void AccessibilityManager::UpdateHighContrastFromPref() { 663 if (!profile_) 664 return; 665 666 const bool enabled = profile_->GetPrefs()->GetBoolean( 667 prefs::kAccessibilityHighContrastEnabled); 668 669 if (high_contrast_enabled_ == enabled) 670 return; 671 672 high_contrast_enabled_ = enabled; 673 674 AccessibilityStatusEventDetails details( 675 ACCESSIBILITY_TOGGLE_HIGH_CONTRAST_MODE, 676 enabled, 677 ash::A11Y_NOTIFICATION_NONE); 678 679 NotifyAccessibilityStatusChanged(details); 680 681 #if !defined(USE_ATHENA) 682 683 #if defined(USE_ASH) 684 ash::Shell::GetInstance()->high_contrast_controller()->SetEnabled(enabled); 685 #endif 686 687 #if defined(OS_CHROMEOS) 688 ash::Shell::GetInstance()->SetCursorCompositingEnabled( 689 ShouldEnableCursorCompositing()); 690 #endif 691 692 #endif 693 } 694 695 void AccessibilityManager::OnLocaleChanged() { 696 if (!profile_) 697 return; 698 699 if (!IsSpokenFeedbackEnabled()) 700 return; 701 702 // If the system locale changes and spoken feedback is enabled, 703 // reload ChromeVox so that it switches its internal translations 704 // to the new language. 705 EnableSpokenFeedback(false, ash::A11Y_NOTIFICATION_NONE); 706 EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_NONE); 707 } 708 709 void AccessibilityManager::PlayEarcon(int sound_key) { 710 DCHECK(sound_key < chromeos::SOUND_COUNT); 711 ash::PlaySystemSoundIfSpokenFeedback(sound_key); 712 } 713 714 bool AccessibilityManager::IsHighContrastEnabled() { 715 return high_contrast_enabled_; 716 } 717 718 void AccessibilityManager::EnableAutoclick(bool enabled) { 719 if (!profile_) 720 return; 721 722 PrefService* pref_service = profile_->GetPrefs(); 723 pref_service->SetBoolean(prefs::kAccessibilityAutoclickEnabled, enabled); 724 pref_service->CommitPendingWrite(); 725 } 726 727 bool AccessibilityManager::IsAutoclickEnabled() { 728 return autoclick_enabled_; 729 } 730 731 void AccessibilityManager::UpdateAutoclickFromPref() { 732 bool enabled = 733 profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityAutoclickEnabled); 734 735 if (autoclick_enabled_ == enabled) 736 return; 737 autoclick_enabled_ = enabled; 738 739 #if defined(USE_ASH) && !defined(USE_ATHENA) 740 ash::Shell::GetInstance()->autoclick_controller()->SetEnabled(enabled); 741 #endif 742 } 743 744 void AccessibilityManager::SetAutoclickDelay(int delay_ms) { 745 if (!profile_) 746 return; 747 748 PrefService* pref_service = profile_->GetPrefs(); 749 pref_service->SetInteger(prefs::kAccessibilityAutoclickDelayMs, delay_ms); 750 pref_service->CommitPendingWrite(); 751 } 752 753 int AccessibilityManager::GetAutoclickDelay() const { 754 return autoclick_delay_ms_; 755 } 756 757 void AccessibilityManager::UpdateAutoclickDelayFromPref() { 758 int autoclick_delay_ms = 759 profile_->GetPrefs()->GetInteger(prefs::kAccessibilityAutoclickDelayMs); 760 761 if (autoclick_delay_ms == autoclick_delay_ms_) 762 return; 763 autoclick_delay_ms_ = autoclick_delay_ms; 764 765 #if defined(USE_ASH) && !defined(USE_ATHENA) 766 ash::Shell::GetInstance()->autoclick_controller()->SetAutoclickDelay( 767 autoclick_delay_ms_); 768 #endif 769 } 770 771 void AccessibilityManager::EnableVirtualKeyboard(bool enabled) { 772 if (!profile_) 773 return; 774 775 PrefService* pref_service = profile_->GetPrefs(); 776 pref_service->SetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled, 777 enabled); 778 pref_service->CommitPendingWrite(); 779 } 780 781 bool AccessibilityManager::IsVirtualKeyboardEnabled() { 782 return virtual_keyboard_enabled_; 783 } 784 785 void AccessibilityManager::UpdateVirtualKeyboardFromPref() { 786 if (!profile_) 787 return; 788 789 const bool enabled = profile_->GetPrefs()->GetBoolean( 790 prefs::kAccessibilityVirtualKeyboardEnabled); 791 792 if (virtual_keyboard_enabled_ == enabled) 793 return; 794 virtual_keyboard_enabled_ = enabled; 795 796 AccessibilityStatusEventDetails details( 797 ACCESSIBILITY_TOGGLE_VIRTUAL_KEYBOARD, 798 enabled, 799 ash::A11Y_NOTIFICATION_NONE); 800 801 NotifyAccessibilityStatusChanged(details); 802 803 #if defined(USE_ASH) && !defined(USE_ATHENA) 804 keyboard::SetAccessibilityKeyboardEnabled(enabled); 805 // Note that there are two versions of the on-screen keyboard. A full layout 806 // is provided for accessibility, which includes sticky modifier keys to 807 // enable typing of hotkeys. A compact version is used in touchview mode 808 // to provide a layout with larger keys to facilitate touch typing. In the 809 // event that the a11y keyboard is being disabled, an on-screen keyboard might 810 // still be enabled and a forced reset is required to pick up the layout 811 // change. 812 if (keyboard::IsKeyboardEnabled()) 813 ash::Shell::GetInstance()->CreateKeyboard(); 814 else 815 ash::Shell::GetInstance()->DeactivateKeyboard(); 816 #endif 817 } 818 819 bool AccessibilityManager::IsBrailleDisplayConnected() const { 820 return braille_display_connected_; 821 } 822 823 void AccessibilityManager::CheckBrailleState() { 824 BrailleController* braille_controller = GetBrailleController(); 825 if (!scoped_braille_observer_.IsObserving(braille_controller)) 826 scoped_braille_observer_.Add(braille_controller); 827 BrowserThread::PostTaskAndReplyWithResult( 828 BrowserThread::IO, 829 FROM_HERE, 830 base::Bind(&BrailleController::GetDisplayState, 831 base::Unretained(braille_controller)), 832 base::Bind(&AccessibilityManager::ReceiveBrailleDisplayState, 833 weak_ptr_factory_.GetWeakPtr())); 834 } 835 836 void AccessibilityManager::ReceiveBrailleDisplayState( 837 scoped_ptr<extensions::api::braille_display_private::DisplayState> state) { 838 OnBrailleDisplayStateChanged(*state); 839 } 840 841 void AccessibilityManager::UpdateBrailleImeState() { 842 if (!profile_) 843 return; 844 PrefService* pref_service = profile_->GetPrefs(); 845 std::vector<std::string> preload_engines; 846 base::SplitString(pref_service->GetString(prefs::kLanguagePreloadEngines), 847 ',', 848 &preload_engines); 849 std::vector<std::string>::iterator it = 850 std::find(preload_engines.begin(), 851 preload_engines.end(), 852 extension_misc::kBrailleImeEngineId); 853 bool is_enabled = (it != preload_engines.end()); 854 bool should_be_enabled = 855 (spoken_feedback_enabled_ && braille_display_connected_); 856 if (is_enabled == should_be_enabled) 857 return; 858 if (should_be_enabled) 859 preload_engines.push_back(extension_misc::kBrailleImeEngineId); 860 else 861 preload_engines.erase(it); 862 pref_service->SetString(prefs::kLanguagePreloadEngines, 863 JoinString(preload_engines, ',')); 864 braille_ime_current_ = false; 865 } 866 867 // Overridden from InputMethodManager::Observer. 868 void AccessibilityManager::InputMethodChanged( 869 input_method::InputMethodManager* manager, 870 bool show_message) { 871 #if defined(USE_ASH) && !defined(USE_ATHENA) 872 // Sticky keys is implemented only in ash. 873 // TODO(dpolukhin): support Athena, crbug.com/408733. 874 ash::Shell::GetInstance()->sticky_keys_controller()->SetModifiersEnabled( 875 manager->IsISOLevel5ShiftUsedByCurrentInputMethod(), 876 manager->IsAltGrUsedByCurrentInputMethod()); 877 #endif 878 const chromeos::input_method::InputMethodDescriptor descriptor = 879 manager->GetActiveIMEState()->GetCurrentInputMethod(); 880 braille_ime_current_ = 881 (descriptor.id() == extension_misc::kBrailleImeEngineId); 882 } 883 884 void AccessibilityManager::SetProfile(Profile* profile) { 885 pref_change_registrar_.reset(); 886 local_state_pref_change_registrar_.reset(); 887 888 if (profile) { 889 // TODO(yoshiki): Move following code to PrefHandler. 890 pref_change_registrar_.reset(new PrefChangeRegistrar); 891 pref_change_registrar_->Init(profile->GetPrefs()); 892 pref_change_registrar_->Add( 893 prefs::kAccessibilityLargeCursorEnabled, 894 base::Bind(&AccessibilityManager::UpdateLargeCursorFromPref, 895 base::Unretained(this))); 896 pref_change_registrar_->Add( 897 prefs::kAccessibilityStickyKeysEnabled, 898 base::Bind(&AccessibilityManager::UpdateStickyKeysFromPref, 899 base::Unretained(this))); 900 pref_change_registrar_->Add( 901 prefs::kAccessibilitySpokenFeedbackEnabled, 902 base::Bind(&AccessibilityManager::UpdateSpokenFeedbackFromPref, 903 base::Unretained(this))); 904 pref_change_registrar_->Add( 905 prefs::kAccessibilityHighContrastEnabled, 906 base::Bind(&AccessibilityManager::UpdateHighContrastFromPref, 907 base::Unretained(this))); 908 pref_change_registrar_->Add( 909 prefs::kAccessibilityAutoclickEnabled, 910 base::Bind(&AccessibilityManager::UpdateAutoclickFromPref, 911 base::Unretained(this))); 912 pref_change_registrar_->Add( 913 prefs::kAccessibilityAutoclickDelayMs, 914 base::Bind(&AccessibilityManager::UpdateAutoclickDelayFromPref, 915 base::Unretained(this))); 916 pref_change_registrar_->Add( 917 prefs::kAccessibilityVirtualKeyboardEnabled, 918 base::Bind(&AccessibilityManager::UpdateVirtualKeyboardFromPref, 919 base::Unretained(this))); 920 921 local_state_pref_change_registrar_.reset(new PrefChangeRegistrar); 922 local_state_pref_change_registrar_->Init(g_browser_process->local_state()); 923 local_state_pref_change_registrar_->Add( 924 prefs::kApplicationLocale, 925 base::Bind(&AccessibilityManager::OnLocaleChanged, 926 base::Unretained(this))); 927 928 content::BrowserAccessibilityState::GetInstance()->AddHistogramCallback( 929 base::Bind( 930 &AccessibilityManager::UpdateChromeOSAccessibilityHistograms, 931 base::Unretained(this))); 932 } 933 934 large_cursor_pref_handler_.HandleProfileChanged(profile_, profile); 935 spoken_feedback_pref_handler_.HandleProfileChanged(profile_, profile); 936 high_contrast_pref_handler_.HandleProfileChanged(profile_, profile); 937 autoclick_pref_handler_.HandleProfileChanged(profile_, profile); 938 autoclick_delay_pref_handler_.HandleProfileChanged(profile_, profile); 939 virtual_keyboard_pref_handler_.HandleProfileChanged(profile_, profile); 940 941 bool had_profile = (profile_ != NULL); 942 profile_ = profile; 943 944 if (!had_profile && profile) 945 CheckBrailleState(); 946 else 947 UpdateBrailleImeState(); 948 UpdateLargeCursorFromPref(); 949 UpdateStickyKeysFromPref(); 950 UpdateSpokenFeedbackFromPref(); 951 UpdateHighContrastFromPref(); 952 UpdateAutoclickFromPref(); 953 UpdateAutoclickDelayFromPref(); 954 UpdateVirtualKeyboardFromPref(); 955 } 956 957 void AccessibilityManager::ActiveUserChanged(const std::string& user_id) { 958 SetProfile(ProfileManager::GetActiveUserProfile()); 959 } 960 961 void AccessibilityManager::SetProfileForTest(Profile* profile) { 962 SetProfile(profile); 963 } 964 965 void AccessibilityManager::SetBrailleControllerForTest( 966 BrailleController* controller) { 967 g_braille_controller_for_test = controller; 968 } 969 970 void AccessibilityManager::EnableSystemSounds(bool system_sounds_enabled) { 971 system_sounds_enabled_ = system_sounds_enabled; 972 } 973 974 base::TimeDelta AccessibilityManager::PlayShutdownSound() { 975 if (!system_sounds_enabled_) 976 return base::TimeDelta(); 977 system_sounds_enabled_ = false; 978 #if !defined(USE_ATHENA) 979 if (!ash::PlaySystemSoundIfSpokenFeedback(SOUND_SHUTDOWN)) 980 return base::TimeDelta(); 981 #endif 982 return media::SoundsManager::Get()->GetDuration(SOUND_SHUTDOWN); 983 } 984 985 void AccessibilityManager::InjectChromeVox(RenderViewHost* render_view_host) { 986 LoadChromeVoxExtension(profile_, render_view_host, base::Closure()); 987 } 988 989 scoped_ptr<AccessibilityStatusSubscription> 990 AccessibilityManager::RegisterCallback( 991 const AccessibilityStatusCallback& cb) { 992 return callback_list_.Add(cb); 993 } 994 995 void AccessibilityManager::NotifyAccessibilityStatusChanged( 996 AccessibilityStatusEventDetails& details) { 997 callback_list_.Notify(details); 998 } 999 1000 void AccessibilityManager::UpdateChromeOSAccessibilityHistograms() { 1001 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosSpokenFeedback", 1002 IsSpokenFeedbackEnabled()); 1003 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosHighContrast", 1004 IsHighContrastEnabled()); 1005 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosVirtualKeyboard", 1006 IsVirtualKeyboardEnabled()); 1007 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosStickyKeys", IsStickyKeysEnabled()); 1008 if (MagnificationManager::Get()) { 1009 uint32 type = MagnificationManager::Get()->IsMagnifierEnabled() ? 1010 MagnificationManager::Get()->GetMagnifierType() : 0; 1011 // '0' means magnifier is disabled. 1012 UMA_HISTOGRAM_ENUMERATION("Accessibility.CrosScreenMagnifier", 1013 type, 1014 ash::kMaxMagnifierType + 1); 1015 } 1016 if (profile_) { 1017 const PrefService* const prefs = profile_->GetPrefs(); 1018 UMA_HISTOGRAM_BOOLEAN( 1019 "Accessibility.CrosLargeCursor", 1020 prefs->GetBoolean(prefs::kAccessibilityLargeCursorEnabled)); 1021 UMA_HISTOGRAM_BOOLEAN( 1022 "Accessibility.CrosAlwaysShowA11yMenu", 1023 prefs->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu)); 1024 1025 bool autoclick_enabled = 1026 prefs->GetBoolean(prefs::kAccessibilityAutoclickEnabled); 1027 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosAutoclick", autoclick_enabled); 1028 if (autoclick_enabled) { 1029 // We only want to log the autoclick delay if the user has actually 1030 // enabled autoclick. 1031 UMA_HISTOGRAM_CUSTOM_TIMES( 1032 "Accessibility.CrosAutoclickDelay", 1033 base::TimeDelta::FromMilliseconds( 1034 prefs->GetInteger(prefs::kAccessibilityAutoclickDelayMs)), 1035 base::TimeDelta::FromMilliseconds(1), 1036 base::TimeDelta::FromMilliseconds(3000), 1037 50); 1038 } 1039 } 1040 } 1041 1042 void AccessibilityManager::Observe( 1043 int type, 1044 const content::NotificationSource& source, 1045 const content::NotificationDetails& details) { 1046 switch (type) { 1047 case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE: { 1048 // Update |profile_| when entering the login screen. 1049 Profile* profile = ProfileManager::GetActiveUserProfile(); 1050 if (ProfileHelper::IsSigninProfile(profile)) 1051 SetProfile(profile); 1052 break; 1053 } 1054 case chrome::NOTIFICATION_SESSION_STARTED: 1055 // Update |profile_| when entering a session. 1056 SetProfile(ProfileManager::GetActiveUserProfile()); 1057 1058 // Ensure ChromeVox makes announcements at the start of new sessions. 1059 should_speak_chrome_vox_announcements_on_user_screen_ = true; 1060 1061 // Add a session state observer to be able to monitor session changes. 1062 if (!session_state_observer_.get() && ash::Shell::HasInstance()) 1063 session_state_observer_.reset( 1064 new ash::ScopedSessionStateObserver(this)); 1065 break; 1066 case chrome::NOTIFICATION_PROFILE_DESTROYED: { 1067 // Update |profile_| when exiting a session or shutting down. 1068 Profile* profile = content::Source<Profile>(source).ptr(); 1069 if (profile_ == profile) 1070 SetProfile(NULL); 1071 break; 1072 } 1073 case chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED: { 1074 bool is_screen_locked = *content::Details<bool>(details).ptr(); 1075 if (spoken_feedback_enabled_) { 1076 if (is_screen_locked) 1077 LoadChromeVoxToLockScreen(base::Closure()); 1078 // If spoken feedback was enabled, make sure it is also enabled on 1079 // the user screen. 1080 // The status tray gets verbalized by user screen ChromeVox, so we need 1081 // to load it on the user screen even if the screen is locked. 1082 LoadChromeVoxToUserScreen(base::Closure()); 1083 } 1084 break; 1085 } 1086 } 1087 } 1088 1089 void AccessibilityManager::OnBrailleDisplayStateChanged( 1090 const DisplayState& display_state) { 1091 braille_display_connected_ = display_state.available; 1092 if (braille_display_connected_) { 1093 EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_SHOW); 1094 } 1095 UpdateBrailleImeState(); 1096 1097 AccessibilityStatusEventDetails details( 1098 ACCESSIBILITY_BRAILLE_DISPLAY_CONNECTION_STATE_CHANGED, 1099 braille_display_connected_, 1100 ash::A11Y_NOTIFICATION_SHOW); 1101 NotifyAccessibilityStatusChanged(details); 1102 } 1103 1104 void AccessibilityManager::OnBrailleKeyEvent(const KeyEvent& event) { 1105 // Ensure the braille IME is active on braille keyboard (dots) input. 1106 if ((event.command == 1107 extensions::api::braille_display_private::KEY_COMMAND_DOTS) && 1108 !braille_ime_current_) { 1109 input_method::InputMethodManager::Get() 1110 ->GetActiveIMEState() 1111 ->ChangeInputMethod(extension_misc::kBrailleImeEngineId, 1112 false /* show_message */); 1113 } 1114 } 1115 1116 void AccessibilityManager::PostLoadChromeVox(Profile* profile) { 1117 // Do any setup work needed immediately after ChromeVox actually loads. 1118 if (system_sounds_enabled_) 1119 ash::PlaySystemSoundAlways(SOUND_SPOKEN_FEEDBACK_ENABLED); 1120 1121 ExtensionAccessibilityEventRouter::GetInstance()-> 1122 OnChromeVoxLoadStateChanged(profile_, 1123 IsSpokenFeedbackEnabled(), 1124 chrome_vox_loaded_on_lock_screen_ || 1125 should_speak_chrome_vox_announcements_on_user_screen_); 1126 1127 should_speak_chrome_vox_announcements_on_user_screen_ = 1128 chrome_vox_loaded_on_lock_screen_; 1129 } 1130 1131 void AccessibilityManager::PostUnloadChromeVox(Profile* profile) { 1132 // Do any teardown work needed immediately after ChromeVox actually unloads. 1133 if (system_sounds_enabled_) 1134 ash::PlaySystemSoundAlways(SOUND_SPOKEN_FEEDBACK_DISABLED); 1135 // Clear the accessibility focus ring. 1136 AccessibilityFocusRingController::GetInstance()->SetFocusRing( 1137 std::vector<gfx::Rect>()); 1138 } 1139 1140 } // namespace chromeos 1141