// 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