// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
#include "ash/autoclick/autoclick_controller.h"
#include "ash/high_contrast/high_contrast_controller.h"
#include "ash/metrics/user_metrics_recorder.h"
#include "ash/session_state_delegate.h"
#include "ash/shell.h"
#include "ash/system/tray/system_tray_notifier.h"
#include "ash/wm/event_rewriter_event_filter.h"
#include "ash/wm/sticky_keys.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram.h"
#include "base/path_service.h"
#include "base/prefs/pref_member.h"
#include "base/prefs/pref_service.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/accessibility/accessibility_extension_api.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/accessibility/magnification_manager.h"
#include "chrome/browser/chromeos/login/login_display_host.h"
#include "chrome/browser/chromeos/login/login_display_host_impl.h"
#include "chrome/browser/chromeos/login/screen_locker.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/chromeos/login/webui_login_view.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/api/experimental_accessibility.h"
#include "chrome/common/extensions/extension_messages.h"
#include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h"
#include "chrome/common/pref_names.h"
#include "chromeos/audio/chromeos_sounds.h"
#include "chromeos/login/login_state.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "extensions/browser/file_reader.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_resource.h"
#include "grit/browser_resources.h"
#include "grit/generated_resources.h"
#include "media/audio/sounds/sounds_manager.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
using content::BrowserThread;
using content::RenderViewHost;
using extensions::api::braille_display_private::BrailleController;
using extensions::api::braille_display_private::DisplayState;
namespace chromeos {
namespace {
static chromeos::AccessibilityManager* g_accessibility_manager = NULL;
static BrailleController* g_braille_controller_for_test = NULL;
BrailleController* GetBrailleController() {
return g_braille_controller_for_test
? g_braille_controller_for_test
: BrailleController::GetInstance();
}
base::FilePath GetChromeVoxPath() {
base::FilePath path;
if (!PathService::Get(chrome::DIR_RESOURCES, &path))
NOTREACHED();
path = path.Append(extension_misc::kChromeVoxExtensionPath);
return path;
}
// Helper class that directly loads an extension's content scripts into
// all of the frames corresponding to a given RenderViewHost.
class ContentScriptLoader {
public:
// Initialize the ContentScriptLoader with the ID of the extension
// and the RenderViewHost where the scripts should be loaded.
ContentScriptLoader(const std::string& extension_id,
int render_process_id,
int render_view_id)
: extension_id_(extension_id),
render_process_id_(render_process_id),
render_view_id_(render_view_id) {}
// Call this once with the ExtensionResource corresponding to each
// content script to be loaded.
void AppendScript(extensions::ExtensionResource resource) {
resources_.push(resource);
}
// Finally, call this method once to fetch all of the resources and
// load them. This method will delete this object when done.
void Run() {
if (resources_.empty()) {
delete this;
return;
}
extensions::ExtensionResource resource = resources_.front();
resources_.pop();
scoped_refptr<FileReader> reader(new FileReader(resource, base::Bind(
&ContentScriptLoader::OnFileLoaded, base::Unretained(this))));
reader->Start();
}
private:
void OnFileLoaded(bool success, const std::string& data) {
if (success) {
ExtensionMsg_ExecuteCode_Params params;
params.request_id = 0;
params.extension_id = extension_id_;
params.is_javascript = true;
params.code = data;
params.run_at = extensions::UserScript::DOCUMENT_IDLE;
params.all_frames = true;
params.in_main_world = false;
RenderViewHost* render_view_host =
RenderViewHost::FromID(render_process_id_, render_view_id_);
if (render_view_host) {
render_view_host->Send(new ExtensionMsg_ExecuteCode(
render_view_host->GetRoutingID(), params));
}
}
Run();
}
std::string extension_id_;
int render_process_id_;
int render_view_id_;
std::queue<extensions::ExtensionResource> resources_;
};
void LoadChromeVoxExtension(Profile* profile, content::WebUI* login_web_ui) {
ExtensionService* extension_service =
extensions::ExtensionSystem::Get(profile)->extension_service();
std::string extension_id =
extension_service->component_loader()->AddChromeVoxExtension();
if (login_web_ui) {
ExtensionService* extension_service =
extensions::ExtensionSystem::Get(profile)->extension_service();
const extensions::Extension* extension =
extension_service->extensions()->GetByID(extension_id);
RenderViewHost* render_view_host =
login_web_ui->GetWebContents()->GetRenderViewHost();
// Set a flag to tell ChromeVox that it's just been enabled,
// so that it won't interrupt our speech feedback enabled message.
ExtensionMsg_ExecuteCode_Params params;
params.request_id = 0;
params.extension_id = extension->id();
params.is_javascript = true;
params.code = "window.INJECTED_AFTER_LOAD = true;";
params.run_at = extensions::UserScript::DOCUMENT_IDLE;
params.all_frames = true;
params.in_main_world = false;
render_view_host->Send(new ExtensionMsg_ExecuteCode(
render_view_host->GetRoutingID(), params));
// Inject ChromeVox' content scripts.
ContentScriptLoader* loader = new ContentScriptLoader(
extension->id(), render_view_host->GetProcess()->GetID(),
render_view_host->GetRoutingID());
const extensions::UserScriptList& content_scripts =
extensions::ContentScriptsInfo::GetContentScripts(extension);
for (size_t i = 0; i < content_scripts.size(); i++) {
const extensions::UserScript& script = content_scripts[i];
for (size_t j = 0; j < script.js_scripts().size(); ++j) {
const extensions::UserScript::File &file = script.js_scripts()[j];
extensions::ExtensionResource resource = extension->GetResource(
file.relative_path());
loader->AppendScript(resource);
}
}
loader->Run(); // It cleans itself up when done.
}
}
void UnloadChromeVoxExtension(Profile* profile) {
base::FilePath path = GetChromeVoxPath();
ExtensionService* extension_service =
extensions::ExtensionSystem::Get(profile)->extension_service();
extension_service->component_loader()->Remove(path);
}
} // namespace
///////////////////////////////////////////////////////////////////////////////
// AccessibilityStatusEventDetails
AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
bool enabled,
ash::AccessibilityNotificationVisibility notify)
: enabled(enabled),
magnifier_type(ash::kDefaultMagnifierType),
notify(notify) {}
AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
bool enabled,
ash::MagnifierType magnifier_type,
ash::AccessibilityNotificationVisibility notify)
: enabled(enabled),
magnifier_type(magnifier_type),
notify(notify) {}
///////////////////////////////////////////////////////////////////////////////
//
// AccessibilityManager::PrefHandler
AccessibilityManager::PrefHandler::PrefHandler(const char* pref_path)
: pref_path_(pref_path) {}
AccessibilityManager::PrefHandler::~PrefHandler() {}
void AccessibilityManager::PrefHandler::HandleProfileChanged(
Profile* previous_profile, Profile* current_profile) {
// Returns if the current profile is null.
if (!current_profile)
return;
// If the user set a pref value on the login screen and is now starting a
// session with a new profile, copy the pref value to the profile.
if ((previous_profile &&
ProfileHelper::IsSigninProfile(previous_profile) &&
current_profile->IsNewProfile() &&
!ProfileHelper::IsSigninProfile(current_profile)) ||
// Special case for Guest mode:
// Guest mode launches a guest-mode browser process before session starts,
// so the previous profile is null.
(!previous_profile &&
current_profile->IsGuestSession())) {
// Returns if the pref has not been set by the user.
const PrefService::Preference* pref = ProfileHelper::GetSigninProfile()->
GetPrefs()->FindPreference(pref_path_);
if (!pref || !pref->IsUserControlled())
return;
// Copy the pref value from the signin screen.
const base::Value* value_on_login = pref->GetValue();
PrefService* user_prefs = current_profile->GetPrefs();
user_prefs->Set(pref_path_, *value_on_login);
}
}
///////////////////////////////////////////////////////////////////////////////
//
// AccessibilityManager
// static
void AccessibilityManager::Initialize() {
CHECK(g_accessibility_manager == NULL);
g_accessibility_manager = new AccessibilityManager();
}
// static
void AccessibilityManager::Shutdown() {
CHECK(g_accessibility_manager);
delete g_accessibility_manager;
g_accessibility_manager = NULL;
}
// static
AccessibilityManager* AccessibilityManager::Get() {
return g_accessibility_manager;
}
AccessibilityManager::AccessibilityManager()
: profile_(NULL),
chrome_vox_loaded_on_lock_screen_(false),
chrome_vox_loaded_on_user_screen_(false),
large_cursor_pref_handler_(prefs::kLargeCursorEnabled),
spoken_feedback_pref_handler_(prefs::kSpokenFeedbackEnabled),
high_contrast_pref_handler_(prefs::kHighContrastEnabled),
autoclick_pref_handler_(prefs::kAutoclickEnabled),
autoclick_delay_pref_handler_(prefs::kAutoclickDelayMs),
large_cursor_enabled_(false),
sticky_keys_enabled_(false),
spoken_feedback_enabled_(false),
high_contrast_enabled_(false),
autoclick_enabled_(false),
autoclick_delay_ms_(ash::AutoclickController::kDefaultAutoclickDelayMs),
spoken_feedback_notification_(ash::A11Y_NOTIFICATION_NONE),
weak_ptr_factory_(this),
should_speak_chrome_vox_announcements_on_user_screen_(true),
system_sounds_enabled_(false) {
notification_registrar_.Add(this,
chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
content::NotificationService::AllSources());
notification_registrar_.Add(this,
chrome::NOTIFICATION_SESSION_STARTED,
content::NotificationService::AllSources());
notification_registrar_.Add(this,
chrome::NOTIFICATION_PROFILE_DESTROYED,
content::NotificationService::AllSources());
notification_registrar_.Add(this,
chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED,
content::NotificationService::AllSources());
GetBrailleController()->AddObserver(this);
ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
media::SoundsManager* manager = media::SoundsManager::Get();
manager->Initialize(SOUND_SHUTDOWN,
bundle.GetRawDataResource(IDR_SOUND_SHUTDOWN_WAV));
manager->Initialize(
SOUND_SPOKEN_FEEDBACK_ENABLED,
bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_ENABLED_WAV));
manager->Initialize(
SOUND_SPOKEN_FEEDBACK_DISABLED,
bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_DISABLED_WAV));
}
AccessibilityManager::~AccessibilityManager() {
CHECK(this == g_accessibility_manager);
}
bool AccessibilityManager::ShouldShowAccessibilityMenu() {
// If any of the loaded profiles has an accessibility feature turned on - or
// enforced to always show the menu - we return true to show the menu.
std::vector<Profile*> profiles =
g_browser_process->profile_manager()->GetLoadedProfiles();
for (std::vector<Profile*>::iterator it = profiles.begin();
it != profiles.end();
++it) {
PrefService* pref_service = (*it)->GetPrefs();
if (pref_service->GetBoolean(prefs::kStickyKeysEnabled) ||
pref_service->GetBoolean(prefs::kLargeCursorEnabled) ||
pref_service->GetBoolean(prefs::kSpokenFeedbackEnabled) ||
pref_service->GetBoolean(prefs::kHighContrastEnabled) ||
pref_service->GetBoolean(prefs::kAutoclickEnabled) ||
pref_service->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu) ||
pref_service->GetBoolean(prefs::kScreenMagnifierEnabled))
return true;
}
return false;
}
void AccessibilityManager::EnableLargeCursor(bool enabled) {
if (!profile_)
return;
PrefService* pref_service = profile_->GetPrefs();
pref_service->SetBoolean(prefs::kLargeCursorEnabled, enabled);
pref_service->CommitPendingWrite();
}
void AccessibilityManager::UpdateLargeCursorFromPref() {
if (!profile_)
return;
const bool enabled =
profile_->GetPrefs()->GetBoolean(prefs::kLargeCursorEnabled);
if (large_cursor_enabled_ == enabled)
return;
large_cursor_enabled_ = enabled;
AccessibilityStatusEventDetails details(enabled, ash::A11Y_NOTIFICATION_NONE);
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_CROS_ACCESSIBILITY_TOGGLE_LARGE_CURSOR,
content::NotificationService::AllSources(),
content::Details<AccessibilityStatusEventDetails>(&details));
#if defined(USE_ASH)
// Large cursor is implemented only in ash.
ash::Shell::GetInstance()->cursor_manager()->SetCursorSet(
enabled ? ui::CURSOR_SET_LARGE : ui::CURSOR_SET_NORMAL);
#endif
}
bool AccessibilityManager::IsIncognitoAllowed() {
UserManager* user_manager = UserManager::Get();
// Supervised users can't create incognito-mode windows.
return !(user_manager->IsLoggedInAsLocallyManagedUser());
}
bool AccessibilityManager::IsLargeCursorEnabled() {
return large_cursor_enabled_;
}
void AccessibilityManager::EnableStickyKeys(bool enabled) {
if (!profile_)
return;
PrefService* pref_service = profile_->GetPrefs();
pref_service->SetBoolean(prefs::kStickyKeysEnabled, enabled);
pref_service->CommitPendingWrite();
}
bool AccessibilityManager::IsStickyKeysEnabled() {
return sticky_keys_enabled_;
}
void AccessibilityManager::UpdateStickyKeysFromPref() {
if (!profile_)
return;
const bool enabled =
profile_->GetPrefs()->GetBoolean(prefs::kStickyKeysEnabled);
if (sticky_keys_enabled_ == enabled)
return;
sticky_keys_enabled_ = enabled;
#if defined(USE_ASH)
// Sticky keys is implemented only in ash.
ash::Shell::GetInstance()->sticky_keys()->Enable(enabled);
#endif
}
void AccessibilityManager::EnableSpokenFeedback(
bool enabled,
ash::AccessibilityNotificationVisibility notify) {
if (!profile_)
return;
ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(
enabled ? ash::UMA_STATUS_AREA_ENABLE_SPOKEN_FEEDBACK
: ash::UMA_STATUS_AREA_DISABLE_SPOKEN_FEEDBACK);
spoken_feedback_notification_ = notify;
PrefService* pref_service = profile_->GetPrefs();
pref_service->SetBoolean(
prefs::kSpokenFeedbackEnabled, enabled);
pref_service->CommitPendingWrite();
spoken_feedback_notification_ = ash::A11Y_NOTIFICATION_NONE;
}
void AccessibilityManager::UpdateSpokenFeedbackFromPref() {
if (!profile_)
return;
const bool enabled =
profile_->GetPrefs()->GetBoolean(prefs::kSpokenFeedbackEnabled);
if (spoken_feedback_enabled_ == enabled)
return;
spoken_feedback_enabled_ = enabled;
ExtensionAccessibilityEventRouter::GetInstance()->
SetAccessibilityEnabled(enabled);
AccessibilityStatusEventDetails details(enabled,
spoken_feedback_notification_);
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_CROS_ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK,
content::NotificationService::AllSources(),
content::Details<AccessibilityStatusEventDetails>(&details));
if (enabled) {
LoadChromeVox();
} else {
UnloadChromeVox();
}
}
void AccessibilityManager::LoadChromeVox() {
ScreenLocker* screen_locker = ScreenLocker::default_screen_locker();
if (screen_locker && screen_locker->locked()) {
// If on the lock screen, loads ChromeVox only to the lock screen as for
// now. On unlock, it will be loaded to the user screen.
// (see. AccessibilityManager::Observe())
LoadChromeVoxToLockScreen();
} else {
LoadChromeVoxToUserScreen();
}
PostLoadChromeVox(profile_);
}
void AccessibilityManager::LoadChromeVoxToUserScreen() {
if (chrome_vox_loaded_on_user_screen_)
return;
// Determine whether an OOBE screen is currently being shown. If so,
// ChromeVox will be injected directly into that screen.
content::WebUI* login_web_ui = NULL;
if (ProfileHelper::IsSigninProfile(profile_)) {
LoginDisplayHost* login_display_host = LoginDisplayHostImpl::default_host();
if (login_display_host) {
WebUILoginView* web_ui_login_view =
login_display_host->GetWebUILoginView();
if (web_ui_login_view)
login_web_ui = web_ui_login_view->GetWebUI();
}
}
LoadChromeVoxExtension(profile_, login_web_ui);
chrome_vox_loaded_on_user_screen_ = true;
}
void AccessibilityManager::LoadChromeVoxToLockScreen() {
if (chrome_vox_loaded_on_lock_screen_)
return;
ScreenLocker* screen_locker = ScreenLocker::default_screen_locker();
if (screen_locker && screen_locker->locked()) {
content::WebUI* lock_web_ui = screen_locker->GetAssociatedWebUI();
if (lock_web_ui) {
Profile* profile = Profile::FromWebUI(lock_web_ui);
LoadChromeVoxExtension(profile, lock_web_ui);
chrome_vox_loaded_on_lock_screen_ = true;
}
}
}
void AccessibilityManager::UnloadChromeVox() {
if (chrome_vox_loaded_on_lock_screen_)
UnloadChromeVoxFromLockScreen();
if (chrome_vox_loaded_on_user_screen_) {
UnloadChromeVoxExtension(profile_);
chrome_vox_loaded_on_user_screen_ = false;
}
PostUnloadChromeVox(profile_);
}
void AccessibilityManager::UnloadChromeVoxFromLockScreen() {
// Lock screen uses the signin progile.
Profile* signin_profile = ProfileHelper::GetSigninProfile();
UnloadChromeVoxExtension(signin_profile);
chrome_vox_loaded_on_lock_screen_ = false;
}
bool AccessibilityManager::IsSpokenFeedbackEnabled() {
return spoken_feedback_enabled_;
}
void AccessibilityManager::ToggleSpokenFeedback(
ash::AccessibilityNotificationVisibility notify) {
EnableSpokenFeedback(!IsSpokenFeedbackEnabled(), notify);
}
void AccessibilityManager::EnableHighContrast(bool enabled) {
if (!profile_)
return;
PrefService* pref_service = profile_->GetPrefs();
pref_service->SetBoolean(prefs::kHighContrastEnabled, enabled);
pref_service->CommitPendingWrite();
}
void AccessibilityManager::UpdateHighContrastFromPref() {
if (!profile_)
return;
const bool enabled =
profile_->GetPrefs()->GetBoolean(prefs::kHighContrastEnabled);
if (high_contrast_enabled_ == enabled)
return;
high_contrast_enabled_ = enabled;
AccessibilityStatusEventDetails detail(enabled, ash::A11Y_NOTIFICATION_NONE);
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_CROS_ACCESSIBILITY_TOGGLE_HIGH_CONTRAST_MODE,
content::NotificationService::AllSources(),
content::Details<AccessibilityStatusEventDetails>(&detail));
#if defined(USE_ASH)
ash::Shell::GetInstance()->high_contrast_controller()->SetEnabled(enabled);
#endif
}
void AccessibilityManager::LocalePrefChanged() {
if (!profile_)
return;
if (!IsSpokenFeedbackEnabled())
return;
// If the system locale changes and spoken feedback is enabled,
// reload ChromeVox so that it switches its internal translations
// to the new language.
EnableSpokenFeedback(false, ash::A11Y_NOTIFICATION_NONE);
EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_NONE);
}
bool AccessibilityManager::IsHighContrastEnabled() {
return high_contrast_enabled_;
}
void AccessibilityManager::EnableAutoclick(bool enabled) {
if (!profile_)
return;
PrefService* pref_service = profile_->GetPrefs();
pref_service->SetBoolean(prefs::kAutoclickEnabled, enabled);
pref_service->CommitPendingWrite();
}
bool AccessibilityManager::IsAutoclickEnabled() {
return autoclick_enabled_;
}
void AccessibilityManager::UpdateAutoclickFromPref() {
bool enabled =
profile_->GetPrefs()->GetBoolean(prefs::kAutoclickEnabled);
if (autoclick_enabled_ == enabled)
return;
autoclick_enabled_ = enabled;
#if defined(USE_ASH)
ash::Shell::GetInstance()->autoclick_controller()->SetEnabled(enabled);
#endif
}
void AccessibilityManager::SetAutoclickDelay(int delay_ms) {
if (!profile_)
return;
PrefService* pref_service = profile_->GetPrefs();
pref_service->SetInteger(prefs::kAutoclickDelayMs, delay_ms);
pref_service->CommitPendingWrite();
}
int AccessibilityManager::GetAutoclickDelay() const {
return autoclick_delay_ms_;
}
void AccessibilityManager::UpdateAutoclickDelayFromPref() {
int autoclick_delay_ms =
profile_->GetPrefs()->GetInteger(prefs::kAutoclickDelayMs);
if (autoclick_delay_ms == autoclick_delay_ms_)
return;
autoclick_delay_ms_ = autoclick_delay_ms;
#if defined(USE_ASH)
ash::Shell::GetInstance()->autoclick_controller()->SetAutoclickDelay(
autoclick_delay_ms_);
#endif
}
void AccessibilityManager::CheckBrailleState() {
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::IO, FROM_HERE, base::Bind(
&BrailleController::GetDisplayState,
base::Unretained(GetBrailleController())),
base::Bind(&AccessibilityManager::ReceiveBrailleDisplayState,
weak_ptr_factory_.GetWeakPtr()));
}
void AccessibilityManager::ReceiveBrailleDisplayState(
scoped_ptr<extensions::api::braille_display_private::DisplayState> state) {
OnDisplayStateChanged(*state);
}
void AccessibilityManager::SetProfile(Profile* profile) {
pref_change_registrar_.reset();
local_state_pref_change_registrar_.reset();
if (profile) {
// TODO(yoshiki): Move following code to PrefHandler.
pref_change_registrar_.reset(new PrefChangeRegistrar);
pref_change_registrar_->Init(profile->GetPrefs());
pref_change_registrar_->Add(
prefs::kLargeCursorEnabled,
base::Bind(&AccessibilityManager::UpdateLargeCursorFromPref,
base::Unretained(this)));
pref_change_registrar_->Add(
prefs::kStickyKeysEnabled,
base::Bind(&AccessibilityManager::UpdateStickyKeysFromPref,
base::Unretained(this)));
pref_change_registrar_->Add(
prefs::kSpokenFeedbackEnabled,
base::Bind(&AccessibilityManager::UpdateSpokenFeedbackFromPref,
base::Unretained(this)));
pref_change_registrar_->Add(
prefs::kHighContrastEnabled,
base::Bind(&AccessibilityManager::UpdateHighContrastFromPref,
base::Unretained(this)));
pref_change_registrar_->Add(
prefs::kAutoclickEnabled,
base::Bind(&AccessibilityManager::UpdateAutoclickFromPref,
base::Unretained(this)));
pref_change_registrar_->Add(
prefs::kAutoclickDelayMs,
base::Bind(&AccessibilityManager::UpdateAutoclickDelayFromPref,
base::Unretained(this)));
local_state_pref_change_registrar_.reset(new PrefChangeRegistrar);
local_state_pref_change_registrar_->Init(g_browser_process->local_state());
local_state_pref_change_registrar_->Add(
prefs::kApplicationLocale,
base::Bind(&AccessibilityManager::LocalePrefChanged,
base::Unretained(this)));
content::BrowserAccessibilityState::GetInstance()->AddHistogramCallback(
base::Bind(
&AccessibilityManager::UpdateChromeOSAccessibilityHistograms,
base::Unretained(this)));
}
large_cursor_pref_handler_.HandleProfileChanged(profile_, profile);
spoken_feedback_pref_handler_.HandleProfileChanged(profile_, profile);
high_contrast_pref_handler_.HandleProfileChanged(profile_, profile);
autoclick_pref_handler_.HandleProfileChanged(profile_, profile);
autoclick_delay_pref_handler_.HandleProfileChanged(profile_, profile);
if (!profile_ && profile)
CheckBrailleState();
profile_ = profile;
UpdateLargeCursorFromPref();
UpdateStickyKeysFromPref();
UpdateSpokenFeedbackFromPref();
UpdateHighContrastFromPref();
UpdateAutoclickFromPref();
UpdateAutoclickDelayFromPref();
}
void AccessibilityManager::ActiveUserChanged(const std::string& user_id) {
SetProfile(ProfileManager::GetActiveUserProfile());
}
void AccessibilityManager::SetProfileForTest(Profile* profile) {
SetProfile(profile);
}
void AccessibilityManager::SetBrailleControllerForTest(
BrailleController* controller) {
g_braille_controller_for_test = controller;
}
void AccessibilityManager::EnableSystemSounds(bool system_sounds_enabled) {
system_sounds_enabled_ = system_sounds_enabled;
}
base::TimeDelta AccessibilityManager::PlayShutdownSound() {
if (!IsSpokenFeedbackEnabled() || !system_sounds_enabled_)
return base::TimeDelta();
system_sounds_enabled_ = false;
media::SoundsManager* manager = media::SoundsManager::Get();
manager->Play(SOUND_SHUTDOWN);
return manager->GetDuration(SOUND_SHUTDOWN);
}
void AccessibilityManager::UpdateChromeOSAccessibilityHistograms() {
UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosSpokenFeedback",
IsSpokenFeedbackEnabled());
UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosHighContrast",
IsHighContrastEnabled());
UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosVirtualKeyboard",
accessibility::IsVirtualKeyboardEnabled());
if (MagnificationManager::Get()) {
uint32 type = MagnificationManager::Get()->IsMagnifierEnabled() ?
MagnificationManager::Get()->GetMagnifierType() : 0;
// '0' means magnifier is disabled.
UMA_HISTOGRAM_ENUMERATION("Accessibility.CrosScreenMagnifier",
type,
ash::kMaxMagnifierType + 1);
}
if (profile_) {
const PrefService* const prefs = profile_->GetPrefs();
UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosLargeCursor",
prefs->GetBoolean(prefs::kLargeCursorEnabled));
UMA_HISTOGRAM_BOOLEAN(
"Accessibility.CrosAlwaysShowA11yMenu",
prefs->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu));
bool autoclick_enabled = prefs->GetBoolean(prefs::kAutoclickEnabled);
UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosAutoclick", autoclick_enabled);
if (autoclick_enabled) {
// We only want to log the autoclick delay if the user has actually
// enabled autoclick.
UMA_HISTOGRAM_CUSTOM_TIMES(
"Accessibility.CrosAutoclickDelay",
base::TimeDelta::FromMilliseconds(
prefs->GetInteger(prefs::kAutoclickDelayMs)),
base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMilliseconds(3000),
50);
}
}
}
void AccessibilityManager::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE: {
// Update |profile_| when entering the login screen.
Profile* profile = ProfileManager::GetActiveUserProfile();
if (ProfileHelper::IsSigninProfile(profile))
SetProfile(profile);
break;
}
case chrome::NOTIFICATION_SESSION_STARTED:
// Update |profile_| when entering a session.
SetProfile(ProfileManager::GetActiveUserProfile());
// Ensure ChromeVox makes announcements at the start of new sessions.
should_speak_chrome_vox_announcements_on_user_screen_ = true;
// Add a session state observer to be able to monitor session changes.
if (!session_state_observer_.get() && ash::Shell::HasInstance())
session_state_observer_.reset(
new ash::ScopedSessionStateObserver(this));
break;
case chrome::NOTIFICATION_PROFILE_DESTROYED: {
// Update |profile_| when exiting a session or shutting down.
Profile* profile = content::Source<Profile>(source).ptr();
if (profile_ == profile)
SetProfile(NULL);
break;
}
case chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED: {
bool is_screen_locked = *content::Details<bool>(details).ptr();
if (spoken_feedback_enabled_) {
if (is_screen_locked) {
LoadChromeVoxToLockScreen();
// Status tray gets verbalized by user screen ChromeVox, so we need
// this as well.
LoadChromeVoxToUserScreen();
} else {
// Lock screen destroys its resources; no need for us to explicitly
// unload ChromeVox.
chrome_vox_loaded_on_lock_screen_ = false;
// However, if spoken feedback was enabled, also enable it on the user
// screen.
LoadChromeVoxToUserScreen();
}
}
break;
}
}
}
void AccessibilityManager::OnDisplayStateChanged(
const DisplayState& display_state) {
if (display_state.available)
EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_SHOW);
}
void AccessibilityManager::PostLoadChromeVox(Profile* profile) {
// Do any setup work needed immediately after ChromeVox actually loads.
PlaySound(SOUND_SPOKEN_FEEDBACK_ENABLED);
ExtensionAccessibilityEventRouter::GetInstance()->
OnChromeVoxLoadStateChanged(profile_,
IsSpokenFeedbackEnabled(),
chrome_vox_loaded_on_lock_screen_ ||
should_speak_chrome_vox_announcements_on_user_screen_);
should_speak_chrome_vox_announcements_on_user_screen_ =
chrome_vox_loaded_on_lock_screen_;
}
void AccessibilityManager::PostUnloadChromeVox(Profile* profile) {
// Do any teardown work needed immediately after ChromeVox actually unloads.
PlaySound(SOUND_SPOKEN_FEEDBACK_DISABLED);
}
void AccessibilityManager::PlaySound(int sound_key) const {
media::SoundsManager::Get()->Play(sound_key);
}
} // namespace chromeos