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