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