blob: 4546fbac44753284ca46c3680e01939c4beb27a3 [file] [log] [blame]
[email protected]1dfebfc2013-06-04 03:14:321// 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
[email protected]1c881562013-10-12 07:52:587#include "ash/autoclick/autoclick_controller.h"
[email protected]1dfebfc2013-06-04 03:14:328#include "ash/high_contrast/high_contrast_controller.h"
[email protected]5d2ea362013-12-13 08:10:189#include "ash/metrics/user_metrics_recorder.h"
[email protected]1dfebfc2013-06-04 03:14:3210#include "ash/shell.h"
11#include "ash/system/tray/system_tray_notifier.h"
[email protected]93a534e2013-06-20 16:41:4412#include "ash/wm/event_rewriter_event_filter.h"
[email protected]f94c6aa2013-11-08 01:18:5413#include "ash/wm/sticky_keys.h"
[email protected]1dfebfc2013-06-04 03:14:3214#include "base/memory/scoped_ptr.h"
15#include "base/memory/singleton.h"
16#include "base/metrics/histogram.h"
17#include "base/prefs/pref_member.h"
18#include "base/prefs/pref_service.h"
[email protected]0b5a0d2b2013-10-30 03:43:4019#include "base/time/time.h"
[email protected]0aae2fe2013-07-05 07:04:4220#include "base/values.h"
[email protected]1dfebfc2013-06-04 03:14:3221#include "chrome/browser/accessibility/accessibility_extension_api.h"
22#include "chrome/browser/browser_process.h"
[email protected]fdf40f3e2013-07-11 23:55:4623#include "chrome/browser/chrome_notification_types.h"
[email protected]1dfebfc2013-06-04 03:14:3224#include "chrome/browser/chromeos/accessibility/magnification_manager.h"
[email protected]681d0532013-06-11 12:52:5025#include "chrome/browser/chromeos/login/login_display_host.h"
26#include "chrome/browser/chromeos/login/login_display_host_impl.h"
27#include "chrome/browser/chromeos/login/screen_locker.h"
[email protected]5ecf6122013-10-31 12:24:0928#include "chrome/browser/chromeos/login/user_manager.h"
[email protected]681d0532013-06-11 12:52:5029#include "chrome/browser/chromeos/login/webui_login_view.h"
[email protected]e6f20642013-06-11 17:05:4630#include "chrome/browser/chromeos/profiles/profile_helper.h"
[email protected]1dfebfc2013-06-04 03:14:3231#include "chrome/browser/extensions/component_loader.h"
32#include "chrome/browser/extensions/extension_service.h"
33#include "chrome/browser/extensions/extension_system.h"
34#include "chrome/browser/profiles/profile.h"
35#include "chrome/browser/profiles/profile_manager.h"
36#include "chrome/browser/speech/tts_controller.h"
[email protected]0e9504d2013-11-05 02:33:2937#include "chrome/common/extensions/api/experimental_accessibility.h"
[email protected]1dfebfc2013-06-04 03:14:3238#include "chrome/common/extensions/extension_messages.h"
39#include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h"
40#include "chrome/common/pref_names.h"
[email protected]ce89d9392013-12-11 21:05:2041#include "chromeos/audio/chromeos_sounds.h"
[email protected]0aae2fe2013-07-05 07:04:4242#include "chromeos/login/login_state.h"
[email protected]1dfebfc2013-06-04 03:14:3243#include "content/public/browser/browser_accessibility_state.h"
[email protected]8c79e3f2013-10-30 01:05:1444#include "content/public/browser/browser_thread.h"
[email protected]9cf54f362013-07-01 07:28:1245#include "content/public/browser/notification_details.h"
[email protected]1dfebfc2013-06-04 03:14:3246#include "content/public/browser/notification_service.h"
[email protected]9cf54f362013-07-01 07:28:1247#include "content/public/browser/notification_source.h"
[email protected]1dfebfc2013-06-04 03:14:3248#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/file_reader.h"
[email protected]e4452d32013-11-15 23:07:4153#include "extensions/common/extension.h"
[email protected]1dfebfc2013-06-04 03:14:3254#include "extensions/common/extension_resource.h"
55#include "grit/browser_resources.h"
56#include "grit/generated_resources.h"
[email protected]12b0a842013-12-04 20:48:1357#include "media/audio/sounds/sounds_manager.h"
[email protected]1dfebfc2013-06-04 03:14:3258#include "ui/base/l10n/l10n_util.h"
59#include "ui/base/resource/resource_bundle.h"
60
[email protected]8c79e3f2013-10-30 01:05:1461using content::BrowserThread;
[email protected]1dfebfc2013-06-04 03:14:3262using content::RenderViewHost;
[email protected]8c79e3f2013-10-30 01:05:1463using extensions::api::braille_display_private::BrailleController;
64using extensions::api::braille_display_private::DisplayState;
[email protected]1dfebfc2013-06-04 03:14:3265
66namespace chromeos {
67
68namespace {
69
70static chromeos::AccessibilityManager* g_accessibility_manager = NULL;
71
[email protected]8c79e3f2013-10-30 01:05:1472static BrailleController* g_braille_controller_for_test = NULL;
73
74BrailleController* GetBrailleController() {
75 return g_braille_controller_for_test
76 ? g_braille_controller_for_test
77 : BrailleController::GetInstance();
78}
79
[email protected]1dfebfc2013-06-04 03:14:3280// Helper class that directly loads an extension's content scripts into
81// all of the frames corresponding to a given RenderViewHost.
82class ContentScriptLoader {
83 public:
84 // Initialize the ContentScriptLoader with the ID of the extension
85 // and the RenderViewHost where the scripts should be loaded.
86 ContentScriptLoader(const std::string& extension_id,
87 int render_process_id,
88 int render_view_id)
89 : extension_id_(extension_id),
90 render_process_id_(render_process_id),
91 render_view_id_(render_view_id) {}
92
93 // Call this once with the ExtensionResource corresponding to each
94 // content script to be loaded.
95 void AppendScript(extensions::ExtensionResource resource) {
96 resources_.push(resource);
97 }
98
[email protected]5ecf6122013-10-31 12:24:0999 // Finally, call this method once to fetch all of the resources and
[email protected]1dfebfc2013-06-04 03:14:32100 // load them. This method will delete this object when done.
101 void Run() {
102 if (resources_.empty()) {
103 delete this;
104 return;
105 }
106
107 extensions::ExtensionResource resource = resources_.front();
108 resources_.pop();
109 scoped_refptr<FileReader> reader(new FileReader(resource, base::Bind(
110 &ContentScriptLoader::OnFileLoaded, base::Unretained(this))));
111 reader->Start();
112 }
113
114 private:
115 void OnFileLoaded(bool success, const std::string& data) {
116 if (success) {
117 ExtensionMsg_ExecuteCode_Params params;
118 params.request_id = 0;
119 params.extension_id = extension_id_;
120 params.is_javascript = true;
121 params.code = data;
122 params.run_at = extensions::UserScript::DOCUMENT_IDLE;
123 params.all_frames = true;
124 params.in_main_world = false;
125
126 RenderViewHost* render_view_host =
127 RenderViewHost::FromID(render_process_id_, render_view_id_);
128 if (render_view_host) {
129 render_view_host->Send(new ExtensionMsg_ExecuteCode(
130 render_view_host->GetRoutingID(), params));
131 }
132 }
133 Run();
134 }
135
136 std::string extension_id_;
137 int render_process_id_;
138 int render_view_id_;
139 std::queue<extensions::ExtensionResource> resources_;
140};
141
[email protected]b0a2ce32013-07-23 15:24:53142void LoadChromeVoxExtension(Profile* profile, content::WebUI* login_web_ui) {
143 ExtensionService* extension_service =
144 extensions::ExtensionSystem::Get(profile)->extension_service();
145 base::FilePath path = base::FilePath(extension_misc::kChromeVoxExtensionPath);
146 std::string extension_id =
147 extension_service->component_loader()->Add(IDR_CHROMEVOX_MANIFEST,
148 path);
149 if (login_web_ui) {
150 ExtensionService* extension_service =
151 extensions::ExtensionSystem::Get(profile)->extension_service();
152 const extensions::Extension* extension =
153 extension_service->extensions()->GetByID(extension_id);
154
155 RenderViewHost* render_view_host =
156 login_web_ui->GetWebContents()->GetRenderViewHost();
157 // Set a flag to tell ChromeVox that it's just been enabled,
158 // so that it won't interrupt our speech feedback enabled message.
159 ExtensionMsg_ExecuteCode_Params params;
160 params.request_id = 0;
161 params.extension_id = extension->id();
162 params.is_javascript = true;
163 params.code = "window.INJECTED_AFTER_LOAD = true;";
164 params.run_at = extensions::UserScript::DOCUMENT_IDLE;
165 params.all_frames = true;
166 params.in_main_world = false;
167 render_view_host->Send(new ExtensionMsg_ExecuteCode(
168 render_view_host->GetRoutingID(), params));
169
170 // Inject ChromeVox' content scripts.
171 ContentScriptLoader* loader = new ContentScriptLoader(
172 extension->id(), render_view_host->GetProcess()->GetID(),
173 render_view_host->GetRoutingID());
174
175 const extensions::UserScriptList& content_scripts =
176 extensions::ContentScriptsInfo::GetContentScripts(extension);
177 for (size_t i = 0; i < content_scripts.size(); i++) {
178 const extensions::UserScript& script = content_scripts[i];
179 for (size_t j = 0; j < script.js_scripts().size(); ++j) {
180 const extensions::UserScript::File &file = script.js_scripts()[j];
181 extensions::ExtensionResource resource = extension->GetResource(
182 file.relative_path());
183 loader->AppendScript(resource);
184 }
185 }
186 loader->Run(); // It cleans itself up when done.
187 }
[email protected]b0a2ce32013-07-23 15:24:53188}
189
190void UnloadChromeVoxExtension(Profile* profile) {
191 ExtensionService* extension_service =
192 extensions::ExtensionSystem::Get(profile)->extension_service();
193 base::FilePath path = base::FilePath(extension_misc::kChromeVoxExtensionPath);
194 extension_service->component_loader()->Remove(path);
[email protected]b0a2ce32013-07-23 15:24:53195}
196
[email protected]1dfebfc2013-06-04 03:14:32197} // namespace
198
199///////////////////////////////////////////////////////////////////////////////
200// AccessibilityStatusEventDetails
201
202AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
203 bool enabled,
204 ash::AccessibilityNotificationVisibility notify)
205 : enabled(enabled),
206 magnifier_type(ash::kDefaultMagnifierType),
207 notify(notify) {}
208
209AccessibilityStatusEventDetails::AccessibilityStatusEventDetails(
210 bool enabled,
211 ash::MagnifierType magnifier_type,
212 ash::AccessibilityNotificationVisibility notify)
213 : enabled(enabled),
214 magnifier_type(magnifier_type),
215 notify(notify) {}
216
217///////////////////////////////////////////////////////////////////////////////
218//
[email protected]0aae2fe2013-07-05 07:04:42219// AccessibilityManager::PrefHandler
220
221AccessibilityManager::PrefHandler::PrefHandler(const char* pref_path)
222 : pref_path_(pref_path) {}
223
224AccessibilityManager::PrefHandler::~PrefHandler() {}
225
226void AccessibilityManager::PrefHandler::HandleProfileChanged(
227 Profile* previous_profile, Profile* current_profile) {
228 // Returns if the current profile is null.
229 if (!current_profile)
230 return;
231
232 // If the user set a pref value on the login screen and is now starting a
233 // session with a new profile, copy the pref value to the profile.
234 if ((previous_profile &&
235 ProfileHelper::IsSigninProfile(previous_profile) &&
236 current_profile->IsNewProfile() &&
237 !ProfileHelper::IsSigninProfile(current_profile)) ||
238 // Special case for Guest mode:
239 // Guest mode launches a guest-mode browser process before session starts,
240 // so the previous profile is null.
241 (!previous_profile &&
242 current_profile->IsGuestSession())) {
243 // Returns if the pref has not been set by the user.
244 const PrefService::Preference* pref = ProfileHelper::GetSigninProfile()->
245 GetPrefs()->FindPreference(pref_path_);
246 if (!pref || !pref->IsUserControlled())
247 return;
248
249 // Copy the pref value from the signin screen.
250 const base::Value* value_on_login = pref->GetValue();
251 PrefService* user_prefs = current_profile->GetPrefs();
252 user_prefs->Set(pref_path_, *value_on_login);
253 }
254}
255
256///////////////////////////////////////////////////////////////////////////////
257//
[email protected]1dfebfc2013-06-04 03:14:32258// AccessibilityManager
259
260// static
261void AccessibilityManager::Initialize() {
262 CHECK(g_accessibility_manager == NULL);
263 g_accessibility_manager = new AccessibilityManager();
264}
265
266// static
267void AccessibilityManager::Shutdown() {
268 CHECK(g_accessibility_manager);
269 delete g_accessibility_manager;
270 g_accessibility_manager = NULL;
271}
272
273// static
274AccessibilityManager* AccessibilityManager::Get() {
275 return g_accessibility_manager;
276}
277
[email protected]d80f19302013-06-07 13:12:14278AccessibilityManager::AccessibilityManager()
279 : profile_(NULL),
[email protected]b0a2ce32013-07-23 15:24:53280 chrome_vox_loaded_on_lock_screen_(false),
281 chrome_vox_loaded_on_user_screen_(false),
[email protected]0aae2fe2013-07-05 07:04:42282 large_cursor_pref_handler_(prefs::kLargeCursorEnabled),
283 spoken_feedback_pref_handler_(prefs::kSpokenFeedbackEnabled),
284 high_contrast_pref_handler_(prefs::kHighContrastEnabled),
[email protected]1c881562013-10-12 07:52:58285 autoclick_pref_handler_(prefs::kAutoclickEnabled),
[email protected]84d652d2013-10-23 13:57:57286 autoclick_delay_pref_handler_(prefs::kAutoclickDelayMs),
[email protected]d80f19302013-06-07 13:12:14287 large_cursor_enabled_(false),
[email protected]93a534e2013-06-20 16:41:44288 sticky_keys_enabled_(false),
[email protected]d80f19302013-06-07 13:12:14289 spoken_feedback_enabled_(false),
290 high_contrast_enabled_(false),
[email protected]186ba06bc2013-11-26 18:02:19291 autoclick_enabled_(false),
[email protected]84d652d2013-10-23 13:57:57292 autoclick_delay_ms_(ash::AutoclickController::kDefaultAutoclickDelayMs),
[email protected]8c79e3f2013-10-30 01:05:14293 spoken_feedback_notification_(ash::A11Y_NOTIFICATION_NONE),
[email protected]0e9504d2013-11-05 02:33:29294 weak_ptr_factory_(this),
[email protected]ce89d9392013-12-11 21:05:20295 should_speak_chrome_vox_announcements_on_user_screen_(true),
296 system_sounds_enabled_(false) {
[email protected]1dfebfc2013-06-04 03:14:32297 notification_registrar_.Add(this,
[email protected]093d9ba2013-07-23 19:26:21298 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
[email protected]e6f20642013-06-11 17:05:46299 content::NotificationService::AllSources());
300 notification_registrar_.Add(this,
[email protected]1dfebfc2013-06-04 03:14:32301 chrome::NOTIFICATION_SESSION_STARTED,
302 content::NotificationService::AllSources());
303 notification_registrar_.Add(this,
[email protected]1dfebfc2013-06-04 03:14:32304 chrome::NOTIFICATION_PROFILE_DESTROYED,
305 content::NotificationService::AllSources());
[email protected]b0a2ce32013-07-23 15:24:53306 notification_registrar_.Add(this,
307 chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED,
308 content::NotificationService::AllSources());
[email protected]9d6c91362013-12-07 21:56:27309 notification_registrar_.Add(this,
310 chrome::NOTIFICATION_EXTENSION_REMOVED,
311 content::NotificationService::AllSources());
312 notification_registrar_.Add(this,
313 chrome::NOTIFICATION_EXTENSION_UNLOADED,
314 content::NotificationService::AllSources());
[email protected]8c79e3f2013-10-30 01:05:14315 GetBrailleController()->AddObserver(this);
[email protected]12b0a842013-12-04 20:48:13316
317 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
[email protected]ce89d9392013-12-11 21:05:20318 media::SoundsManager* manager = media::SoundsManager::Get();
319 manager->Initialize(SOUND_SHUTDOWN,
320 bundle.GetRawDataResource(IDR_SOUND_SHUTDOWN_WAV));
321 manager->Initialize(
322 SOUND_SPOKEN_FEEDBACK_ENABLED,
323 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_ENABLED_WAV));
324 manager->Initialize(
325 SOUND_SPOKEN_FEEDBACK_DISABLED,
326 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_DISABLED_WAV));
[email protected]1dfebfc2013-06-04 03:14:32327}
328
329AccessibilityManager::~AccessibilityManager() {
330 CHECK(this == g_accessibility_manager);
[email protected]9d6c91362013-12-07 21:56:27331
332 // Component extensions don't always notify us when they're unloaded. Ensure
333 // we clean up ChromeVox observers here.
[email protected]186ba06bc2013-11-26 18:02:19334 if (profile_) {
335 extensions::ExtensionSystem::Get(profile_)->
336 event_router()->UnregisterObserver(this);
337 }
[email protected]1dfebfc2013-06-04 03:14:32338}
339
[email protected]8126b812013-06-06 03:49:17340void AccessibilityManager::EnableLargeCursor(bool enabled) {
[email protected]d80f19302013-06-07 13:12:14341 if (!profile_)
342 return;
343
344 PrefService* pref_service = profile_->GetPrefs();
345 pref_service->SetBoolean(prefs::kLargeCursorEnabled, enabled);
346 pref_service->CommitPendingWrite();
347}
348
349void AccessibilityManager::UpdateLargeCursorFromPref() {
350 if (!profile_)
351 return;
352
353 const bool enabled =
354 profile_->GetPrefs()->GetBoolean(prefs::kLargeCursorEnabled);
355
[email protected]8126b812013-06-06 03:49:17356 if (large_cursor_enabled_ == enabled)
357 return;
358
359 large_cursor_enabled_ = enabled;
360
[email protected]846be6e2013-06-07 08:03:26361 AccessibilityStatusEventDetails details(enabled, ash::A11Y_NOTIFICATION_NONE);
362 content::NotificationService::current()->Notify(
363 chrome::NOTIFICATION_CROS_ACCESSIBILITY_TOGGLE_LARGE_CURSOR,
364 content::NotificationService::AllSources(),
365 content::Details<AccessibilityStatusEventDetails>(&details));
366
[email protected]8126b812013-06-06 03:49:17367#if defined(USE_ASH)
368 // Large cursor is implemented only in ash.
[email protected]a7c93e32013-09-03 08:50:30369 ash::Shell::GetInstance()->cursor_manager()->SetCursorSet(
370 enabled ? ui::CURSOR_SET_LARGE : ui::CURSOR_SET_NORMAL);
[email protected]8126b812013-06-06 03:49:17371#endif
372}
373
[email protected]5ecf6122013-10-31 12:24:09374bool AccessibilityManager::IsIncognitoAllowed() {
375 UserManager* user_manager = UserManager::Get();
376 // Supervised users can't create incognito-mode windows.
377 return !(user_manager->IsLoggedInAsLocallyManagedUser());
378}
379
[email protected]8126b812013-06-06 03:49:17380bool AccessibilityManager::IsLargeCursorEnabled() {
381 return large_cursor_enabled_;
382}
383
[email protected]93a534e2013-06-20 16:41:44384void AccessibilityManager::EnableStickyKeys(bool enabled) {
385 if (!profile_)
386 return;
387 PrefService* pref_service = profile_->GetPrefs();
388 pref_service->SetBoolean(prefs::kStickyKeysEnabled, enabled);
389 pref_service->CommitPendingWrite();
390}
391
392bool AccessibilityManager::IsStickyKeysEnabled() {
393 return sticky_keys_enabled_;
394}
395
396void AccessibilityManager::UpdateStickyKeysFromPref() {
397 if (!profile_)
398 return;
399
400 const bool enabled =
401 profile_->GetPrefs()->GetBoolean(prefs::kStickyKeysEnabled);
402
403 if (sticky_keys_enabled_ == enabled)
404 return;
405
406 sticky_keys_enabled_ = enabled;
407#if defined(USE_ASH)
408 // Sticky keys is implemented only in ash.
[email protected]f94c6aa2013-11-08 01:18:54409 ash::Shell::GetInstance()->sticky_keys()->Enable(enabled);
[email protected]93a534e2013-06-20 16:41:44410#endif
411}
412
[email protected]1dfebfc2013-06-04 03:14:32413void AccessibilityManager::EnableSpokenFeedback(
414 bool enabled,
[email protected]1dfebfc2013-06-04 03:14:32415 ash::AccessibilityNotificationVisibility notify) {
[email protected]1dfebfc2013-06-04 03:14:32416 if (!profile_)
417 return;
418
[email protected]5d2ea362013-12-13 08:10:18419 ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(
420 enabled ? ash::UMA_STATUS_AREA_ENABLE_SPOKEN_FEEDBACK
421 : ash::UMA_STATUS_AREA_DISABLE_SPOKEN_FEEDBACK);
422
[email protected]d80f19302013-06-07 13:12:14423 spoken_feedback_notification_ = notify;
424
[email protected]1dfebfc2013-06-04 03:14:32425 PrefService* pref_service = profile_->GetPrefs();
426 pref_service->SetBoolean(
427 prefs::kSpokenFeedbackEnabled, enabled);
428 pref_service->CommitPendingWrite();
[email protected]d80f19302013-06-07 13:12:14429
[email protected]d80f19302013-06-07 13:12:14430 spoken_feedback_notification_ = ash::A11Y_NOTIFICATION_NONE;
431}
432
433void AccessibilityManager::UpdateSpokenFeedbackFromPref() {
434 if (!profile_)
435 return;
436
437 const bool enabled =
438 profile_->GetPrefs()->GetBoolean(prefs::kSpokenFeedbackEnabled);
439
440 if (spoken_feedback_enabled_ == enabled)
441 return;
442
443 spoken_feedback_enabled_ = enabled;
444
[email protected]1dfebfc2013-06-04 03:14:32445 ExtensionAccessibilityEventRouter::GetInstance()->
446 SetAccessibilityEnabled(enabled);
447
[email protected]d80f19302013-06-07 13:12:14448 AccessibilityStatusEventDetails details(enabled,
449 spoken_feedback_notification_);
[email protected]1dfebfc2013-06-04 03:14:32450 content::NotificationService::current()->Notify(
451 chrome::NOTIFICATION_CROS_ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK,
452 content::NotificationService::AllSources(),
453 content::Details<AccessibilityStatusEventDetails>(&details));
454
[email protected]0e9504d2013-11-05 02:33:29455 if (enabled) {
[email protected]b0a2ce32013-07-23 15:24:53456 LoadChromeVox();
[email protected]0e9504d2013-11-05 02:33:29457 } else {
458 ExtensionAccessibilityEventRouter::GetInstance()->
459 OnChromeVoxLoadStateChanged(profile_, false, false);
460 }
[email protected]b0a2ce32013-07-23 15:24:53461}
462
463void AccessibilityManager::LoadChromeVox() {
[email protected]9d6c91362013-12-07 21:56:27464 SetUpPreLoadChromeVox(profile_);
465
[email protected]b0a2ce32013-07-23 15:24:53466 ScreenLocker* screen_locker = ScreenLocker::default_screen_locker();
467 if (screen_locker && screen_locker->locked()) {
468 // If on the lock screen, loads ChromeVox only to the lock screen as for
469 // now. On unlock, it will be loaded to the user screen.
470 // (see. AccessibilityManager::Observe())
471 LoadChromeVoxToLockScreen();
472 return;
473 }
474
475 LoadChromeVoxToUserScreen();
476}
477
478void AccessibilityManager::LoadChromeVoxToUserScreen() {
479 if (chrome_vox_loaded_on_user_screen_)
480 return;
481
482 // Determine whether an OOBE screen is currently being shown. If so,
483 // ChromeVox will be injected directly into that screen.
[email protected]681d0532013-06-11 12:52:50484 content::WebUI* login_web_ui = NULL;
[email protected]681d0532013-06-11 12:52:50485
[email protected]b0a2ce32013-07-23 15:24:53486 if (ProfileHelper::IsSigninProfile(profile_)) {
487 LoginDisplayHost* login_display_host = LoginDisplayHostImpl::default_host();
488 if (login_display_host) {
489 WebUILoginView* web_ui_login_view =
490 login_display_host->GetWebUILoginView();
491 if (web_ui_login_view)
492 login_web_ui = web_ui_login_view->GetWebUI();
[email protected]1dfebfc2013-06-04 03:14:32493 }
[email protected]1dfebfc2013-06-04 03:14:32494 }
[email protected]b0a2ce32013-07-23 15:24:53495
496 LoadChromeVoxExtension(profile_, login_web_ui);
497 chrome_vox_loaded_on_user_screen_ = true;
498}
499
500void AccessibilityManager::LoadChromeVoxToLockScreen() {
501 if (chrome_vox_loaded_on_lock_screen_)
502 return;
503
504 ScreenLocker* screen_locker = ScreenLocker::default_screen_locker();
505 if (screen_locker && screen_locker->locked()) {
506 content::WebUI* lock_web_ui = screen_locker->GetAssociatedWebUI();
507 if (lock_web_ui) {
508 Profile* profile = Profile::FromWebUI(lock_web_ui);
509 LoadChromeVoxExtension(profile, lock_web_ui);
510 chrome_vox_loaded_on_lock_screen_ = true;
511 }
512 }
513}
514
515void AccessibilityManager::UnloadChromeVox() {
[email protected]ce89d9392013-12-11 21:05:20516 PlaySound(SOUND_SPOKEN_FEEDBACK_DISABLED);
[email protected]b0a2ce32013-07-23 15:24:53517 if (chrome_vox_loaded_on_lock_screen_)
518 UnloadChromeVoxFromLockScreen();
519
520 if (chrome_vox_loaded_on_user_screen_) {
521 UnloadChromeVoxExtension(profile_);
522 chrome_vox_loaded_on_user_screen_ = false;
523 }
524}
525
526void AccessibilityManager::UnloadChromeVoxFromLockScreen() {
527 // Lock screen uses the signin progile.
528 Profile* signin_profile = ProfileHelper::GetSigninProfile();
529 UnloadChromeVoxExtension(signin_profile);
530 chrome_vox_loaded_on_lock_screen_ = false;
[email protected]1dfebfc2013-06-04 03:14:32531}
532
533bool AccessibilityManager::IsSpokenFeedbackEnabled() {
534 return spoken_feedback_enabled_;
535}
536
537void AccessibilityManager::ToggleSpokenFeedback(
[email protected]1dfebfc2013-06-04 03:14:32538 ash::AccessibilityNotificationVisibility notify) {
[email protected]681d0532013-06-11 12:52:50539 EnableSpokenFeedback(!IsSpokenFeedbackEnabled(), notify);
[email protected]1dfebfc2013-06-04 03:14:32540}
541
[email protected]1dfebfc2013-06-04 03:14:32542void AccessibilityManager::EnableHighContrast(bool enabled) {
[email protected]d80f19302013-06-07 13:12:14543 if (!profile_)
544 return;
545
546 PrefService* pref_service = profile_->GetPrefs();
547 pref_service->SetBoolean(prefs::kHighContrastEnabled, enabled);
548 pref_service->CommitPendingWrite();
549}
550
551void AccessibilityManager::UpdateHighContrastFromPref() {
552 if (!profile_)
553 return;
554
555 const bool enabled =
556 profile_->GetPrefs()->GetBoolean(prefs::kHighContrastEnabled);
557
[email protected]1dfebfc2013-06-04 03:14:32558 if (high_contrast_enabled_ == enabled)
559 return;
560
561 high_contrast_enabled_ = enabled;
562
[email protected]1dfebfc2013-06-04 03:14:32563 AccessibilityStatusEventDetails detail(enabled, ash::A11Y_NOTIFICATION_NONE);
564 content::NotificationService::current()->Notify(
565 chrome::NOTIFICATION_CROS_ACCESSIBILITY_TOGGLE_HIGH_CONTRAST_MODE,
566 content::NotificationService::AllSources(),
567 content::Details<AccessibilityStatusEventDetails>(&detail));
568
569#if defined(USE_ASH)
570 ash::Shell::GetInstance()->high_contrast_controller()->SetEnabled(enabled);
571#endif
572}
573
[email protected]d1d5f4f2013-06-14 07:17:09574void AccessibilityManager::LocalePrefChanged() {
575 if (!profile_)
576 return;
577
578 if (!IsSpokenFeedbackEnabled())
579 return;
580
581 // If the system locale changes and spoken feedback is enabled,
582 // reload ChromeVox so that it switches its internal translations
583 // to the new language.
584 EnableSpokenFeedback(false, ash::A11Y_NOTIFICATION_NONE);
585 EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_NONE);
586}
587
[email protected]1dfebfc2013-06-04 03:14:32588bool AccessibilityManager::IsHighContrastEnabled() {
589 return high_contrast_enabled_;
590}
591
[email protected]1c881562013-10-12 07:52:58592void AccessibilityManager::EnableAutoclick(bool enabled) {
593 if (!profile_)
594 return;
595
596 PrefService* pref_service = profile_->GetPrefs();
597 pref_service->SetBoolean(prefs::kAutoclickEnabled, enabled);
598 pref_service->CommitPendingWrite();
599}
600
601bool AccessibilityManager::IsAutoclickEnabled() {
602 return autoclick_enabled_;
603}
604
605void AccessibilityManager::UpdateAutoclickFromPref() {
606 bool enabled =
607 profile_->GetPrefs()->GetBoolean(prefs::kAutoclickEnabled);
608
609 if (autoclick_enabled_ == enabled)
610 return;
611 autoclick_enabled_ = enabled;
612
613#if defined(USE_ASH)
614 ash::Shell::GetInstance()->autoclick_controller()->SetEnabled(enabled);
615#endif
616}
617
[email protected]84d652d2013-10-23 13:57:57618void AccessibilityManager::SetAutoclickDelay(int delay_ms) {
619 if (!profile_)
620 return;
621
622 PrefService* pref_service = profile_->GetPrefs();
623 pref_service->SetInteger(prefs::kAutoclickDelayMs, delay_ms);
624 pref_service->CommitPendingWrite();
625}
626
627int AccessibilityManager::GetAutoclickDelay() const {
628 return autoclick_delay_ms_;
629}
630
631void AccessibilityManager::UpdateAutoclickDelayFromPref() {
632 int autoclick_delay_ms =
633 profile_->GetPrefs()->GetInteger(prefs::kAutoclickDelayMs);
634
635 if (autoclick_delay_ms == autoclick_delay_ms_)
636 return;
637 autoclick_delay_ms_ = autoclick_delay_ms;
638
639#if defined(USE_ASH)
640 ash::Shell::GetInstance()->autoclick_controller()->SetAutoclickDelay(
641 autoclick_delay_ms_);
642#endif
643}
644
[email protected]8c79e3f2013-10-30 01:05:14645void AccessibilityManager::CheckBrailleState() {
646 BrowserThread::PostTaskAndReplyWithResult(
647 BrowserThread::IO, FROM_HERE, base::Bind(
648 &BrailleController::GetDisplayState,
649 base::Unretained(GetBrailleController())),
650 base::Bind(&AccessibilityManager::ReceiveBrailleDisplayState,
651 weak_ptr_factory_.GetWeakPtr()));
652}
653
654void AccessibilityManager::ReceiveBrailleDisplayState(
655 scoped_ptr<extensions::api::braille_display_private::DisplayState> state) {
656 OnDisplayStateChanged(*state);
657}
658
659
[email protected]1dfebfc2013-06-04 03:14:32660void AccessibilityManager::SetProfile(Profile* profile) {
661 pref_change_registrar_.reset();
[email protected]d1d5f4f2013-06-14 07:17:09662 local_state_pref_change_registrar_.reset();
[email protected]1dfebfc2013-06-04 03:14:32663
664 if (profile) {
[email protected]0aae2fe2013-07-05 07:04:42665 // TODO(yoshiki): Move following code to PrefHandler.
[email protected]1dfebfc2013-06-04 03:14:32666 pref_change_registrar_.reset(new PrefChangeRegistrar);
667 pref_change_registrar_->Init(profile->GetPrefs());
668 pref_change_registrar_->Add(
[email protected]8126b812013-06-06 03:49:17669 prefs::kLargeCursorEnabled,
[email protected]d80f19302013-06-07 13:12:14670 base::Bind(&AccessibilityManager::UpdateLargeCursorFromPref,
[email protected]8126b812013-06-06 03:49:17671 base::Unretained(this)));
672 pref_change_registrar_->Add(
[email protected]93a534e2013-06-20 16:41:44673 prefs::kStickyKeysEnabled,
674 base::Bind(&AccessibilityManager::UpdateStickyKeysFromPref,
675 base::Unretained(this)));
676 pref_change_registrar_->Add(
[email protected]1dfebfc2013-06-04 03:14:32677 prefs::kSpokenFeedbackEnabled,
[email protected]d80f19302013-06-07 13:12:14678 base::Bind(&AccessibilityManager::UpdateSpokenFeedbackFromPref,
[email protected]1dfebfc2013-06-04 03:14:32679 base::Unretained(this)));
680 pref_change_registrar_->Add(
681 prefs::kHighContrastEnabled,
[email protected]d80f19302013-06-07 13:12:14682 base::Bind(&AccessibilityManager::UpdateHighContrastFromPref,
[email protected]1dfebfc2013-06-04 03:14:32683 base::Unretained(this)));
[email protected]1c881562013-10-12 07:52:58684 pref_change_registrar_->Add(
685 prefs::kAutoclickEnabled,
686 base::Bind(&AccessibilityManager::UpdateAutoclickFromPref,
687 base::Unretained(this)));
[email protected]84d652d2013-10-23 13:57:57688 pref_change_registrar_->Add(
689 prefs::kAutoclickDelayMs,
690 base::Bind(&AccessibilityManager::UpdateAutoclickDelayFromPref,
691 base::Unretained(this)));
[email protected]1dfebfc2013-06-04 03:14:32692
[email protected]d1d5f4f2013-06-14 07:17:09693 local_state_pref_change_registrar_.reset(new PrefChangeRegistrar);
694 local_state_pref_change_registrar_->Init(g_browser_process->local_state());
695 local_state_pref_change_registrar_->Add(
696 prefs::kApplicationLocale,
697 base::Bind(&AccessibilityManager::LocalePrefChanged,
698 base::Unretained(this)));
699
[email protected]1dfebfc2013-06-04 03:14:32700 content::BrowserAccessibilityState::GetInstance()->AddHistogramCallback(
701 base::Bind(
702 &AccessibilityManager::UpdateChromeOSAccessibilityHistograms,
703 base::Unretained(this)));
704 }
705
[email protected]0aae2fe2013-07-05 07:04:42706 large_cursor_pref_handler_.HandleProfileChanged(profile_, profile);
707 spoken_feedback_pref_handler_.HandleProfileChanged(profile_, profile);
708 high_contrast_pref_handler_.HandleProfileChanged(profile_, profile);
[email protected]1c881562013-10-12 07:52:58709 autoclick_pref_handler_.HandleProfileChanged(profile_, profile);
[email protected]84d652d2013-10-23 13:57:57710 autoclick_delay_pref_handler_.HandleProfileChanged(profile_, profile);
[email protected]0aae2fe2013-07-05 07:04:42711
[email protected]9d6c91362013-12-07 21:56:27712 if (!profile && profile_) {
713 extensions::ExtensionSystem::Get(profile_)->
714 event_router()->UnregisterObserver(this);
715 }
716
717 if (profile && spoken_feedback_enabled_)
718 SetUpPreLoadChromeVox(profile);
719
[email protected]8c79e3f2013-10-30 01:05:14720 if (!profile_ && profile)
721 CheckBrailleState();
722
[email protected]1dfebfc2013-06-04 03:14:32723 profile_ = profile;
[email protected]d80f19302013-06-07 13:12:14724 UpdateLargeCursorFromPref();
[email protected]93a534e2013-06-20 16:41:44725 UpdateStickyKeysFromPref();
[email protected]d80f19302013-06-07 13:12:14726 UpdateSpokenFeedbackFromPref();
727 UpdateHighContrastFromPref();
[email protected]1c881562013-10-12 07:52:58728 UpdateAutoclickFromPref();
[email protected]84d652d2013-10-23 13:57:57729 UpdateAutoclickDelayFromPref();
[email protected]1dfebfc2013-06-04 03:14:32730}
731
732void AccessibilityManager::SetProfileForTest(Profile* profile) {
733 SetProfile(profile);
734}
735
[email protected]8c79e3f2013-10-30 01:05:14736void AccessibilityManager::SetBrailleControllerForTest(
737 BrailleController* controller) {
738 g_braille_controller_for_test = controller;
739}
740
[email protected]ce89d9392013-12-11 21:05:20741void AccessibilityManager::EnableSystemSounds(bool system_sounds_enabled) {
742 system_sounds_enabled_ = system_sounds_enabled;
743}
744
745base::TimeDelta AccessibilityManager::PlayShutdownSound() {
746 if (!IsSpokenFeedbackEnabled() || !system_sounds_enabled_)
747 return base::TimeDelta();
748 system_sounds_enabled_ = false;
749 media::SoundsManager* manager = media::SoundsManager::Get();
750 manager->Play(SOUND_SHUTDOWN);
751 return manager->GetDuration(SOUND_SHUTDOWN);
752}
753
[email protected]1dfebfc2013-06-04 03:14:32754void AccessibilityManager::UpdateChromeOSAccessibilityHistograms() {
755 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosSpokenFeedback",
756 IsSpokenFeedbackEnabled());
757 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosHighContrast",
758 IsHighContrastEnabled());
759 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosVirtualKeyboard",
760 accessibility::IsVirtualKeyboardEnabled());
761 if (MagnificationManager::Get()) {
762 uint32 type = MagnificationManager::Get()->IsMagnifierEnabled() ?
763 MagnificationManager::Get()->GetMagnifierType() : 0;
764 // '0' means magnifier is disabled.
765 UMA_HISTOGRAM_ENUMERATION("Accessibility.CrosScreenMagnifier",
766 type,
767 ash::kMaxMagnifierType + 1);
768 }
[email protected]ca3a0a82013-06-18 06:52:12769 if (profile_) {
770 const PrefService* const prefs = profile_->GetPrefs();
771 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosLargeCursor",
772 prefs->GetBoolean(prefs::kLargeCursorEnabled));
773 UMA_HISTOGRAM_BOOLEAN(
774 "Accessibility.CrosAlwaysShowA11yMenu",
775 prefs->GetBoolean(prefs::kShouldAlwaysShowAccessibilityMenu));
[email protected]0b5a0d2b2013-10-30 03:43:40776
777 bool autoclick_enabled = prefs->GetBoolean(prefs::kAutoclickEnabled);
778 UMA_HISTOGRAM_BOOLEAN("Accessibility.CrosAutoclick", autoclick_enabled);
779 if (autoclick_enabled) {
780 // We only want to log the autoclick delay if the user has actually
781 // enabled autoclick.
782 UMA_HISTOGRAM_CUSTOM_TIMES(
783 "Accessibility.CrosAutoclickDelay",
784 base::TimeDelta::FromMilliseconds(
785 prefs->GetInteger(prefs::kAutoclickDelayMs)),
786 base::TimeDelta::FromMilliseconds(1),
787 base::TimeDelta::FromMilliseconds(3000),
788 50);
789 }
[email protected]ca3a0a82013-06-18 06:52:12790 }
[email protected]1dfebfc2013-06-04 03:14:32791}
792
793void AccessibilityManager::Observe(
794 int type,
795 const content::NotificationSource& source,
796 const content::NotificationDetails& details) {
797 switch (type) {
[email protected]093d9ba2013-07-23 19:26:21798 case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE: {
[email protected]9cf54f362013-07-01 07:28:12799 // Update |profile_| when entering the login screen.
[email protected]d888cdc2013-06-24 17:06:20800 Profile* profile = ProfileManager::GetDefaultProfile();
801 if (ProfileHelper::IsSigninProfile(profile))
802 SetProfile(profile);
[email protected]1dfebfc2013-06-04 03:14:32803 break;
[email protected]d888cdc2013-06-24 17:06:20804 }
[email protected]e6f20642013-06-11 17:05:46805 case chrome::NOTIFICATION_SESSION_STARTED:
806 // Update |profile_| when entering a session.
807 SetProfile(ProfileManager::GetDefaultProfile());
[email protected]0e9504d2013-11-05 02:33:29808
809 // Ensure ChromeVox makes announcements at the start of new sessions.
810 should_speak_chrome_vox_announcements_on_user_screen_ = true;
[email protected]1dfebfc2013-06-04 03:14:32811 break;
[email protected]1dfebfc2013-06-04 03:14:32812 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
[email protected]e6f20642013-06-11 17:05:46813 // Update |profile_| when exiting a session or shutting down.
814 Profile* profile = content::Source<Profile>(source).ptr();
815 if (profile_ == profile)
816 SetProfile(NULL);
[email protected]1dfebfc2013-06-04 03:14:32817 break;
818 }
[email protected]b0a2ce32013-07-23 15:24:53819 case chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED: {
820 bool is_screen_locked = *content::Details<bool>(details).ptr();
[email protected]9d6c91362013-12-07 21:56:27821 if (spoken_feedback_enabled_) {
822 if (is_screen_locked) {
[email protected]b0a2ce32013-07-23 15:24:53823 LoadChromeVoxToLockScreen();
[email protected]b0a2ce32013-07-23 15:24:53824
[email protected]9d6c91362013-12-07 21:56:27825 // Status tray gets verbalized by user screen ChromeVox, so we need
826 // this as well.
[email protected]b0a2ce32013-07-23 15:24:53827 LoadChromeVoxToUserScreen();
[email protected]9d6c91362013-12-07 21:56:27828 } else {
829 // Lock screen destroys its resources; no need for us to explicitly
830 // unload ChromeVox.
831 chrome_vox_loaded_on_lock_screen_ = false;
832
833 // However, if spoken feedback was enabled, also enable it on the user
834 // screen.
835 LoadChromeVoxToUserScreen();
836 }
[email protected]b0a2ce32013-07-23 15:24:53837 }
[email protected]9d6c91362013-12-07 21:56:27838 break;
839 }
840 case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
841 extensions::UnloadedExtensionInfo* info =
842 content::Details<extensions::UnloadedExtensionInfo>(details).ptr();
843 const extensions::Extension* extension = info->extension;
844 if (extension->id() == extension_misc::kChromeVoxExtensionId) {
845 Profile* profile = content::Source<Profile>(source).ptr();
846 TearDownPostUnloadChromeVox(profile);
847 }
848 break;
849 }
850 case chrome::NOTIFICATION_EXTENSION_REMOVED: {
851 const extensions::Extension* extension =
852 content::Details<const extensions::Extension>(details).ptr();
853 if (extension->id() == extension_misc::kChromeVoxExtensionId) {
854 Profile* profile = content::Source<Profile>(source).ptr();
855 TearDownPostUnloadChromeVox(profile);
856 }
857 break;
[email protected]b0a2ce32013-07-23 15:24:53858 }
[email protected]1dfebfc2013-06-04 03:14:32859 }
860}
861
[email protected]8c79e3f2013-10-30 01:05:14862void AccessibilityManager::OnDisplayStateChanged(
863 const DisplayState& display_state) {
864 if (display_state.available)
865 EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_SHOW);
866}
[email protected]0e9504d2013-11-05 02:33:29867
868void AccessibilityManager::OnListenerAdded(
869 const extensions::EventListenerInfo& details) {
870 if (details.extension_id != extension_misc::kChromeVoxExtensionId)
871 return;
872
873 ExtensionAccessibilityEventRouter::GetInstance()->
874 OnChromeVoxLoadStateChanged(profile_,
875 IsSpokenFeedbackEnabled(),
876 chrome_vox_loaded_on_lock_screen_ ||
877 should_speak_chrome_vox_announcements_on_user_screen_);
878
879 should_speak_chrome_vox_announcements_on_user_screen_ =
880 chrome_vox_loaded_on_lock_screen_;
881}
882
883void AccessibilityManager::OnListenerRemoved(
884 const extensions::EventListenerInfo& details) {
885 if (details.extension_id != extension_misc::kChromeVoxExtensionId)
886 return;
887
888 UnloadChromeVox();
[email protected]12b0a842013-12-04 20:48:13889
890 // It's possible for a user to rapidly toggle ChromeVox on/off state. Load
891 // ChromeVox again if we've been enabled while disabling.
892 if (IsSpokenFeedbackEnabled())
893 LoadChromeVox();
[email protected]0e9504d2013-11-05 02:33:29894}
895
[email protected]9d6c91362013-12-07 21:56:27896void AccessibilityManager::SetUpPreLoadChromeVox(Profile* profile) {
897 // Do any setup work needed immediately before ChromeVox actually loads.
[email protected]ce89d9392013-12-11 21:05:20898 PlaySound(SOUND_SPOKEN_FEEDBACK_ENABLED);
[email protected]9d6c91362013-12-07 21:56:27899
900 if (profile) {
901 extensions::ExtensionSystem::Get(profile)->
902 event_router()->RegisterObserver(this,
903 extensions::api::experimental_accessibility::
904 OnChromeVoxLoadStateChanged::kEventName);
905 }
906}
907
908void AccessibilityManager::TearDownPostUnloadChromeVox(Profile* profile) {
909 // Do any teardown work needed immediately after ChromeVox actually unloads.
910 if (profile) {
911 extensions::ExtensionSystem::Get(profile)->
912 event_router()->UnregisterObserver(this);
913 }
914}
915
[email protected]ce89d9392013-12-11 21:05:20916void AccessibilityManager::PlaySound(int sound_key) const {
917 if (system_sounds_enabled_)
918 media::SoundsManager::Get()->Play(sound_key);
919}
920
[email protected]1dfebfc2013-06-04 03:14:32921} // namespace chromeos