<commit_before>// Copyright (c) 2012 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/input_method/input_method_manager.h"

#include <algorithm>

#include <glib.h>

#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/singleton.h"
#include "base/message_loop.h"
#include "base/process_util.h"
#include "base/string_split.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/input_method/input_method_util.h"
#include "chrome/browser/chromeos/input_method/virtual_keyboard_selector.h"
#include "chrome/browser/chromeos/input_method/xkeyboard.h"
#include "chrome/browser/chromeos/language_preferences.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "googleurl/src/gurl.h"
#include "ui/base/accelerators/accelerator.h"
#include "unicode/uloc.h"

#if !defined(USE_VIRTUAL_KEYBOARD)
#include "chrome/browser/chromeos/input_method/candidate_window.h"
#endif

#if !defined(USE_AURA)
#include "chrome/browser/chromeos/input_method/hotkey_manager.h"
#include <X11/X.h>  // ShiftMask, ControlMask, etc.
#include <X11/Xutil.h>  // for XK_* macros.
#endif

using content::BrowserThread;

namespace {

const char kIBusDaemonPath[] = "/usr/bin/ibus-daemon";

#if !defined(USE_AURA)
const size_t kMaxInputMethodsPerHotkey = 2;

// For hotkey handling.
enum HotkeyEvent {
  // Global hotkeys.
  kPreviousInputMethod = 0,
  kNextInputMethod,
  // Input method specific hotkeys.
  kJapaneseInputMethod,
  kJapaneseLayout,
  kJapaneseInputMethodOrLayout,
  kKoreanInputMethodOrLayout,
};

// Details of the input method specific hotkeys.
const struct InputMethodSpecificHotkeySetting {
  const char* input_method_ids[kMaxInputMethodsPerHotkey];
  int event_id;
  KeySym keysym;
  uint32 modifiers;
  bool trigger_on_press;  // if true. false means 'trigger on release'.
} kInputMethodSpecificHotkeySettings[] = {
  {
    { "mozc-jp" },
    kJapaneseInputMethod,
    XK_Henkan,
    0x0,
    true,
  },
  {
    { "xkb:jp::jpn" },
    kJapaneseLayout,
    XK_Muhenkan,
    0x0,
    true,
  },
  {
    { "mozc-jp", "xkb:jp::jpn" },
    kJapaneseInputMethodOrLayout,
    XK_Zenkaku_Hankaku,
    0x0,
    true,
  },
  {
    { "mozc-hangul", "xkb:kr:kr104:kor" },
    kKoreanInputMethodOrLayout,
    XK_Hangul,
    0x0,
    true,
  },
  {
    { "mozc-hangul", "xkb:kr:kr104:kor" },
    kKoreanInputMethodOrLayout,
    XK_space,
    ShiftMask,
    true,
  },
};

const size_t kInputMethodSpecificHotkeySettingsLen =
    arraysize(kInputMethodSpecificHotkeySettings);
#endif

// Finds a property which has |new_prop.key| from |prop_list|, and replaces the
// property with |new_prop|. Returns true if such a property is found.
bool FindAndUpdateProperty(
    const chromeos::input_method::ImeProperty& new_prop,
    chromeos::input_method::ImePropertyList* prop_list) {
  for (size_t i = 0; i < prop_list->size(); ++i) {
    chromeos::input_method::ImeProperty& prop = prop_list->at(i);
    if (prop.key == new_prop.key) {
      const int saved_id = prop.selection_item_id;
      // Update the list except the radio id. As written in
      // chromeos_input_method.h, |prop.selection_item_id| is dummy.
      prop = new_prop;
      prop.selection_item_id = saved_id;
      return true;
    }
  }
  return false;
}

}  // namespace

namespace chromeos {
namespace input_method {

// The implementation of InputMethodManager.
class InputMethodManagerImpl
    :
#if !defined(USE_AURA)
      public HotkeyManager::Observer,
#endif
      public InputMethodManager,
      public content::NotificationObserver,
#if !defined(USE_VIRTUAL_KEYBOARD)
      public CandidateWindowController::Observer,
#endif
      public IBusController::Observer {
 public:
  InputMethodManagerImpl()
      : ibus_controller_(IBusController::Create()),
        should_launch_ime_(false),
        ime_connected_(false),
        defer_ime_startup_(false),
        enable_auto_ime_shutdown_(true),
        shutting_down_(false),
        ibus_daemon_process_handle_(base::kNullProcessHandle),
        util_(ibus_controller_->GetSupportedInputMethods()),
        xkeyboard_(XKeyboard::Create(util_)),
        ignore_hotkeys_(false) {
    // Observe APP_TERMINATING to stop input method daemon gracefully.
    // We should not use APP_EXITING here since logout might be canceled by
    // JavaScript after APP_EXITING is sent (crosbug.com/11055).
    // Note that even if we fail to stop input method daemon from
    // Chrome in case of a sudden crash, we have a way to do it from an
    // upstart script. See crosbug.com/6515 and crosbug.com/6995 for
    // details.
    notification_registrar_.Add(this, content::NOTIFICATION_APP_TERMINATING,
                                content::NotificationService::AllSources());

    // The observer should be added before Connect() so we can capture the
    // initial connection change.
    ibus_controller_->AddObserver(this);
    ibus_controller_->Connect();

#if !defined(USE_AURA)
    // Initialize extra_hotkeys_.
    for (size_t i = 0; i < kInputMethodSpecificHotkeySettingsLen; ++i) {
      const char* const* input_method_ids =
          kInputMethodSpecificHotkeySettings[i].input_method_ids;
      for (size_t j = 0;
           j < kMaxInputMethodsPerHotkey && input_method_ids[j];
           ++j) {
        extra_hotkeys_.insert(std::make_pair(
            input_method_ids[j], &kInputMethodSpecificHotkeySettings[i]));
      }
    }
#endif

    EnableHotkeys();

#if !defined(USE_AURA)
    hotkey_manager_.AddObserver(this);
#endif
  }

  virtual ~InputMethodManagerImpl() {
#if !defined(USE_AURA)
    hotkey_manager_.RemoveObserver(this);
#endif
    ibus_controller_->RemoveObserver(this);
#if !defined(USE_VIRTUAL_KEYBOARD)
    if (candidate_window_controller_.get())
      candidate_window_controller_->RemoveObserver(this);
#endif
  }

  virtual void AddObserver(InputMethodManager::Observer* observer) {
    observers_.AddObserver(observer);
  }

  virtual void RemoveObserver(InputMethodManager::Observer* observer) {
    observers_.RemoveObserver(observer);
  }

  virtual void AddCandidateWindowObserver(
      InputMethodManager::CandidateWindowObserver* observer) {
    candidate_window_observers_.AddObserver(observer);
  }

  virtual void RemoveCandidateWindowObserver(
      InputMethodManager::CandidateWindowObserver* observer) {
    candidate_window_observers_.RemoveObserver(observer);
  }

  virtual void AddPreLoginPreferenceObserver(
      InputMethodManager::PreferenceObserver* observer) {
    if (!pre_login_preference_observers_.size()) {
      observer->FirstObserverIsAdded(this);
    }
    pre_login_preference_observers_.AddObserver(observer);
  }

  virtual void RemovePreLoginPreferenceObserver(
      InputMethodManager::PreferenceObserver* observer) {
    pre_login_preference_observers_.RemoveObserver(observer);
  }

  virtual void AddPostLoginPreferenceObserver(
      InputMethodManager::PreferenceObserver* observer) {
    if (!post_login_preference_observers_.size()) {
      observer->FirstObserverIsAdded(this);
    }
    post_login_preference_observers_.AddObserver(observer);
  }

  virtual void RemovePostLoginPreferenceObserver(
      InputMethodManager::PreferenceObserver* observer) {
    post_login_preference_observers_.RemoveObserver(observer);
  }

  virtual void AddVirtualKeyboardObserver(VirtualKeyboardObserver* observer) {
    virtual_keyboard_observers_.AddObserver(observer);
  }

  virtual void RemoveVirtualKeyboardObserver(
      VirtualKeyboardObserver* observer) {
    virtual_keyboard_observers_.RemoveObserver(observer);
  }

  virtual InputMethodDescriptors* GetActiveInputMethods() {
    InputMethodDescriptors* result = new InputMethodDescriptors;
    // Build the active input method descriptors from the active input
    // methods cache |active_input_method_ids_|.
    for (size_t i = 0; i < active_input_method_ids_.size(); ++i) {
      const std::string& input_method_id = active_input_method_ids_[i];
      const InputMethodDescriptor* descriptor =
          util_.GetInputMethodDescriptorFromId(input_method_id);
      if (descriptor) {
        result->push_back(*descriptor);
      } else {
        std::map<std::string, InputMethodDescriptor>::
            const_iterator ix = extra_input_method_ids_.find(input_method_id);
        if (ix != extra_input_method_ids_.end()) {
          result->push_back(ix->second);
        } else {
          LOG(ERROR) << "Descriptor is not found for: " << input_method_id;
        }
      }
    }
    // Initially active_input_method_ids_ is empty. In this case, just
    // returns the fallback input method descriptor.
    if (result->empty()) {
      // Since browser_tests call neither SetImeConfig("preload_engines") nor
      // EnableInputMethod(), this path might be taken.
      result->push_back(
          InputMethodDescriptor::GetFallbackInputMethodDescriptor());
    }
    return result;
  }

  virtual size_t GetNumActiveInputMethods() {
    scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods());
    return input_methods->size();
  }

  virtual InputMethodDescriptors* GetSupportedInputMethods() {
    return ibus_controller_->GetSupportedInputMethods();
  }

  virtual void ChangeInputMethod(const std::string& input_method_id) {
    // Changing the input method isn't guaranteed to succeed here, but we
    // should remember the last one regardless. See comments in
    // FlushImeConfig() for details.
    tentative_current_input_method_id_ = input_method_id;

    if (InputMethodUtil::IsKeyboardLayout(input_method_id)
#if !defined(USE_AURA)
        // TODO(yusukes): Remove this code when R18 stable is shipped.
        // crosbug.com/26245
        && ibus_daemon_process_handle_ == base::kNullProcessHandle
#endif
    ) {
      // We shouldn't use SetCurrentKeyboardLayoutByName() here. See
      // comments at ChangeCurrentInputMethod() for details.
      ChangeCurrentInputMethodFromId(input_method_id);
    } else {
      // Otherwise, start the input method daemon, and change the input
      // method via the daemon.
      StartInputMethodDaemon();
      // ChangeInputMethodViaIBus() fails if the IBus daemon is not
      // ready yet. In this case, we'll defer the input method change
      // until the daemon is ready.
      if (!ChangeInputMethodViaIBus(input_method_id)) {
        VLOG(1) << "Failed to change the input method to " << input_method_id
                << " (deferring)";
      }
    }
  }

  virtual void EnableInputMethods(const std::string& language_code,
                                  InputMethodType type,
                                  const std::string& initial_input_method_id) {
    std::vector<std::string> candidates;
    // Add input methods associated with the language.
    util_.GetInputMethodIdsFromLanguageCode(language_code, type, &candidates);
    // Add the hardware keyboard as well. We should always add this so users
    // can use the hardware keyboard on the login screen and the screen locker.
    candidates.push_back(util_.GetHardwareInputMethodId());

    std::vector<std::string> input_method_ids;
    // First, add the initial input method ID, if it's requested, to
    // input_method_ids, so it appears first on the list of active input
    // methods at the input language status menu.
    if (!initial_input_method_id.empty()) {
      input_method_ids.push_back(initial_input_method_id);
    }

    // Add candidates to input_method_ids, while skipping duplicates.
    for (size_t i = 0; i < candidates.size(); ++i) {
      const std::string& candidate = candidates[i];
      // Not efficient, but should be fine, as the two vectors are very
      // short (2-5 items).
      if (std::count(input_method_ids.begin(), input_method_ids.end(),
                     candidate) == 0) {
        input_method_ids.push_back(candidate);
      }
    }

    // Update ibus-daemon setting. Here, we don't save the input method list
    // in the user's preferences.
    ImeConfigValue value;
    value.type = ImeConfigValue::kValueTypeStringList;
    value.string_list_value = input_method_ids;
    SetImeConfig(language_prefs::kGeneralSectionName,
                 language_prefs::kPreloadEnginesConfigName,
                 value);

    // Finaly, change to the initial input method, as needed.
    if (!initial_input_method_id.empty()) {
      ChangeInputMethod(initial_input_method_id);
    }
  }

  virtual void SetImePropertyActivated(const std::string& key,
                                       bool activated) {
    DCHECK(!key.empty());
    ibus_controller_->SetImePropertyActivated(key, activated);
  }

  virtual bool InputMethodIsActivated(const std::string& input_method_id) {
    scoped_ptr<InputMethodDescriptors> active_input_method_descriptors(
        GetActiveInputMethods());
    for (size_t i = 0; i < active_input_method_descriptors->size(); ++i) {
      if (active_input_method_descriptors->at(i).id() == input_method_id) {
        return true;
      }
    }
    return false;
  }

  virtual bool SetImeConfig(const std::string& section,
                            const std::string& config_name,
                            const ImeConfigValue& value) {
    // If the config change is for preload engines, update the active
    // input methods cache |active_input_method_ids_| here. We need to
    // update the cache before actually flushing the config. since we need
    // to return active input methods from GetActiveInputMethods() before
    // the input method daemon starts. For instance, we need to show the
    // list of available input methods (keyboard layouts) on the login
    // screen before the input method starts.
    if (section == language_prefs::kGeneralSectionName &&
        config_name == language_prefs::kPreloadEnginesConfigName &&
        value.type == ImeConfigValue::kValueTypeStringList) {
      active_input_method_ids_ = value.string_list_value;

      std::map<std::string, InputMethodDescriptor>::const_iterator ix;
      for (ix = extra_input_method_ids_.begin();
           ix != extra_input_method_ids_.end(); ++ix) {
        active_input_method_ids_.push_back(ix->first);
      }
#if !defined(USE_AURA)
      UpdateInputMethodSpecificHotkeys();
#endif
    }

    // Before calling FlushImeConfig(), start input method process if necessary.
    MaybeStartInputMethodDaemon(section, config_name, value);

    const ConfigKeyType key = std::make_pair(section, config_name);
    current_config_values_[key] = value;
    if (ime_connected_) {
      pending_config_requests_[key] = value;
      FlushImeConfig();
    }

    if (section == language_prefs::kGeneralSectionName &&
        config_name == language_prefs::kPreloadEnginesConfigName) {
      // Stop input method process if necessary.
      MaybeStopInputMethodDaemon();
    }
    // Change the current keyboard layout if necessary.
    MaybeChangeCurrentKeyboardLayout(section, config_name, value);
    return pending_config_requests_.empty();
  }

  // TODO(yusukes): Support input method specific hotkeys.
  virtual void AddActiveIme(const std::string& id,
                            const std::string& name,
                            const std::vector<std::string>& layouts,
                            const std::string& language) {
    std::string virtual_layouts = JoinString(layouts, ',');

    extra_input_method_ids_[id] = ibus_controller_->CreateInputMethodDescriptor(
        id, name, virtual_layouts, language);
    active_input_method_ids_.push_back(id);

    // Ensure that the input method daemon is running.
    StartInputMethodDaemon();
  }

  virtual void RemoveActiveIme(const std::string& id) {
    std::vector<std::string>::iterator ix =
        std::find(active_input_method_ids_.begin(),
                  active_input_method_ids_.end(),
                  id);
    if (ix != active_input_method_ids_.end()) {
      active_input_method_ids_.erase(ix);
    }
    extra_input_method_ids_.erase(id);

    // Stop the IME daemon if needed.
    MaybeStopInputMethodDaemon();
  }

  virtual bool GetExtraDescriptor(const std::string& id,
                                  InputMethodDescriptor* descriptor) {
    std::map<std::string, InputMethodDescriptor>::const_iterator ix =
        extra_input_method_ids_.find(id);
    if (ix != extra_input_method_ids_.end()) {
      *descriptor = ix->second;
      return true;
    } else {
      return false;
    }
  }

  virtual InputMethodDescriptor previous_input_method() const {
    if (previous_input_method_.id().empty()) {
      return InputMethodDescriptor::GetFallbackInputMethodDescriptor();
    }
    return previous_input_method_;
  }

  virtual InputMethodDescriptor current_input_method() const {
    if (current_input_method_.id().empty()) {
      return InputMethodDescriptor::GetFallbackInputMethodDescriptor();
    }
    return current_input_method_;
  }

  virtual const ImePropertyList& current_ime_properties() const {
    return current_ime_properties_;
  }

  virtual void SendHandwritingStroke(const HandwritingStroke& stroke) {
    ibus_controller_->SendHandwritingStroke(stroke);
  }

  virtual void CancelHandwritingStrokes(int stroke_count) {
    // TODO(yusukes): Rename the function to CancelHandwritingStrokes.
    ibus_controller_->CancelHandwriting(stroke_count);
  }

  virtual void RegisterVirtualKeyboard(const GURL& launch_url,
                                       const std::string& name,
                                       const std::set<std::string>& layouts,
                                       bool is_system) {
    virtual_keyboard_selector_.AddVirtualKeyboard(launch_url,
                                                  name,
                                                  layouts,
                                                  is_system);
  }

  virtual const std::map<GURL, const VirtualKeyboard*>&
  GetUrlToKeyboardMapping() const {
    return virtual_keyboard_selector_.url_to_keyboard();
  }

  virtual const std::multimap<std::string, const VirtualKeyboard*>&
  GetLayoutNameToKeyboardMapping() const {
    return virtual_keyboard_selector_.layout_to_keyboard();
  }

  virtual bool SetVirtualKeyboardPreference(const std::string& input_method_id,
                                            const GURL& extention_url) {
    const bool result = virtual_keyboard_selector_.SetUserPreference(
        input_method_id, extention_url);
    UpdateVirtualKeyboardUI();
    return result;
  }

  virtual void ClearAllVirtualKeyboardPreferences() {
    virtual_keyboard_selector_.ClearAllUserPreferences();
    UpdateVirtualKeyboardUI();
  }

  virtual InputMethodUtil* GetInputMethodUtil() {
    return &util_;
  }

  virtual XKeyboard* GetXKeyboard() {
    return xkeyboard_.get();
  }

#if !defined(USE_AURA)
  virtual HotkeyManager* GetHotkeyManager() {
    return &hotkey_manager_;
  }
#endif

  virtual void HotkeyPressed(HotkeyManager* manager, int event_id) {
#if !defined(USE_AURA)
    const HotkeyEvent event = HotkeyEvent(event_id);
    switch (event) {
      case kNextInputMethod:
        SwitchToNextInputMethod();
        break;
      case kPreviousInputMethod:
        SwitchToPreviousInputMethod();
        break;
      case kJapaneseInputMethod:
      case kJapaneseLayout:
      case kJapaneseInputMethodOrLayout:
      case kKoreanInputMethodOrLayout:
        SwitchToInputMethod(event);
        break;
    }
#endif
  }

  virtual void CandidateWindowOpened() {
    FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver,
                      candidate_window_observers_,
                      CandidateWindowOpened(this));
  }

  virtual void CandidateWindowClosed() {
    FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver,
                      candidate_window_observers_,
                      CandidateWindowClosed(this));
  }

  virtual bool SwitchToNextInputMethod() {
    if (ignore_hotkeys_) {
      return false;
    }

    // Sanity checks.
    if (active_input_method_ids_.empty()) {
      LOG(ERROR) << "active input method is empty";
      return false;
    }
    if (current_input_method_.id().empty()) {
      LOG(ERROR) << "current_input_method_ is unknown";
      return false;
    }

    // Find the next input method.
    std::vector<std::string>::const_iterator iter =
        std::find(active_input_method_ids_.begin(),
                  active_input_method_ids_.end(),
                  current_input_method_.id());
    if (iter != active_input_method_ids_.end()) {
      ++iter;
    }
    if (iter == active_input_method_ids_.end()) {
      iter = active_input_method_ids_.begin();
    }
    ChangeInputMethod(*iter);
    return true;
  }

  virtual bool SwitchToPreviousInputMethod() {
    if (ignore_hotkeys_) {
      return false;
    }

    // Sanity check.
    if (active_input_method_ids_.empty()) {
      LOG(ERROR) << "active input method is empty";
      return false;
    }

    if (previous_input_method_.id().empty() ||
        previous_input_method_.id() == current_input_method_.id()) {
      return SwitchToNextInputMethod();
    }

    std::vector<std::string>::const_iterator iter =
        std::find(active_input_method_ids_.begin(),
                  active_input_method_ids_.end(),
                  previous_input_method_.id());
    if (iter == active_input_method_ids_.end()) {
      // previous_input_method_ is not supported.
      return SwitchToNextInputMethod();
    }
    ChangeInputMethod(*iter);
    return true;
  }

  virtual bool SwitchInputMethod(const ui::Accelerator& accelerator) {
    if (ignore_hotkeys_) {
      return false;
    }

    // Sanity check.
    if (active_input_method_ids_.empty()) {
      LOG(ERROR) << "active input method is empty";
      return false;
    }

    // Get the list of input method ids for the |accelerator|. For example, get
    // { "mozc-hangul", "xkb:kr:kr104:kor" } for ui::VKEY_DBE_SBCSCHAR.
    std::vector<std::string> input_method_ids_to_switch;
    switch (accelerator.key_code()) {
      case ui::VKEY_CONVERT:  // Henkan key on JP106 keyboard
        input_method_ids_to_switch.push_back("mozc-jp");
        break;
      case ui::VKEY_NONCONVERT:  // Muhenkan key on JP106 keyboard
        input_method_ids_to_switch.push_back("xkb:jp::jpn");
        break;
      case ui::VKEY_DBE_SBCSCHAR:  // ZenkakuHankaku key on JP106 keyboard
      case ui::VKEY_DBE_DBCSCHAR:
        input_method_ids_to_switch.push_back("mozc-jp");
        input_method_ids_to_switch.push_back("xkb:jp::jpn");
        break;
      case ui::VKEY_HANGUL:
        input_method_ids_to_switch.push_back("mozc-hangul");
        break;
      case ui::VKEY_SPACE:  // Shift+Space
        input_method_ids_to_switch.push_back("mozc-hangul");
        input_method_ids_to_switch.push_back("xkb:kr:kr104:kor");
        break;
      default:
        NOTREACHED();
        break;
    }
    if (input_method_ids_to_switch.empty()) {
      LOG(ERROR) << "Unexpected VKEY: " << accelerator.key_code();
      return false;
    }

    // Obtain the intersection of input_method_ids_to_switch and
    // active_input_method_ids_. The order of IDs in active_input_method_ids_ is
    // preserved.
    std::vector<std::string> ids;
    for (size_t i = 0; i < input_method_ids_to_switch.size(); ++i) {
      const std::string& id = input_method_ids_to_switch[i];
      if (std::find(active_input_method_ids_.begin(),
                    active_input_method_ids_.end(),
                    id) != active_input_method_ids_.end()) {
        ids.push_back(id);
      }
    }
    if (ids.empty()) {
      // No input method for the accelerator is active. For example, we should
      // just ignore VKEY_HANGUL when mozc-hangul is not active.
      return false;
    }

    // If |current_input_method_| is not in ids, switch to ids[0]. If
    // |current_input_method_| is ids[N], switch to ids[N+1].
    std::vector<std::string>::const_iterator iter =
        std::find(ids.begin(), ids.end(), current_input_method_.id());
    if (iter != ids.end()) {
      ++iter;
    }
    if (iter == ids.end()) {
      iter = ids.begin();
    }
    ChangeInputMethod(*iter);
    return true;  // consume the accelerator.
  }

  virtual void EnableHotkeys() {
#if defined(USE_AURA)
    ignore_hotkeys_ = false;
#else
    // Add Ctrl+space.
    hotkey_manager_.AddHotkey(kPreviousInputMethod,
                              XK_space,
                              ControlMask,
                              true /* trigger on key press */);

    // Add Alt+Shift. For this hotkey, we use "trigger on key release" so that
    // the current IME is not changed on pressing Alt+Shift+something.
    hotkey_manager_.AddHotkey(kNextInputMethod,
                              XK_Shift_L,
                              ShiftMask | Mod1Mask,
                              false /* trigger on key release. */);
    hotkey_manager_.AddHotkey(kNextInputMethod,
                              XK_Shift_R,
                              ShiftMask | Mod1Mask,
                              false);
    hotkey_manager_.AddHotkey(kNextInputMethod,
                              XK_Meta_L,
                              ShiftMask | Mod1Mask,
                              false);
    hotkey_manager_.AddHotkey(kNextInputMethod,
                              XK_Meta_R,
                              ShiftMask | Mod1Mask,
                              false);
    // Input method specific hotkeys will be added in SetImeConfig().
#endif
  }

  virtual void DisableHotkeys() {
#if defined(USE_AURA)
    ignore_hotkeys_ = true;
#else
    hotkey_manager_.RemoveHotkey(kPreviousInputMethod);
    hotkey_manager_.RemoveHotkey(kNextInputMethod);
#endif
  }

  static InputMethodManagerImpl* GetInstance() {
    return Singleton<InputMethodManagerImpl,
        DefaultSingletonTraits<InputMethodManagerImpl> >::get();
  }

 private:
  friend struct DefaultSingletonTraits<InputMethodManagerImpl>;

  // Returns true if the given input method config value is a string list
  // that only contains an input method ID of a keyboard layout.
  bool ContainOnlyKeyboardLayout(const std::vector<std::string>& value) {
    for (size_t i = 0; i < value.size(); ++i) {
      if (!InputMethodUtil::IsKeyboardLayout(value[i])) {
        return false;
      }
    }
    return true;
  }

  // Starts input method daemon based on the |defer_ime_startup_| flag and
  // input method configuration being updated. |section| is a section name of
  // the input method configuration (e.g. "general", "general/hotkey").
  // |config_name| is a name of the configuration (e.g. "preload_engines",
  // "previous_engine"). |value| is the configuration value to be set.
  void MaybeStartInputMethodDaemon(const std::string& section,
                                   const std::string& config_name,
                                   const ImeConfigValue& value) {
    if (section == language_prefs::kGeneralSectionName &&
        config_name == language_prefs::kPreloadEnginesConfigName &&
        value.type == ImeConfigValue::kValueTypeStringList &&
        !value.string_list_value.empty()) {
      // If there is only one input method which is a keyboard layout,
      // we don't start the input method processes.  When
      // |defer_ime_startup_| is true, we don't start it either.
      if (ContainOnlyKeyboardLayout(value.string_list_value) ||
          defer_ime_startup_) {
        // Do not start the input method daemon.
        return;
      }

      // Otherwise, start the input method daemon.
      const bool just_started = StartInputMethodDaemon();
      if (!just_started) {
        // The daemon is already running.
        // Do not |update tentative_current_input_method_id_|.
        return;
      }

      // The daemon has just been started. To select the initial input method
      // engine correctly, update |tentative_current_input_method_id_|.
      if (tentative_current_input_method_id_.empty()) {
        // Since the |current_input_method_| is in the preloaded engine list,
        // switch to the engine. This is necessary ex. for the following case:
        // 1. "xkb:jp::jpn" is enabled. ibus-daemon is not running.
        // 2. A user enabled "mozc" via DOMUI as well. ibus-daemon is started
        //    and the preloaded engine list is set to "mozc,xkb:jp::jpn".
        // 3. ibus-daemon selects "mozc" as its current engine since "mozc" is
        //    on top of the preloaded engine list.
        // 4. Therefore, we have to change the current engine to "xkb:jp::jpn"
        //    explicitly to avoid unexpected engine switch.
        tentative_current_input_method_id_ = current_input_method_.id();
      }

      if (std::find(value.string_list_value.begin(),
                    value.string_list_value.end(),
                    tentative_current_input_method_id_)
          == value.string_list_value.end()) {
        // The |current_input_method_| is NOT in the preloaded engine list.
        // In this case, ibus-daemon will automatically select the first engine
        // in the list, |value.string_list_value[0]|, and send global engine
        // changed signal to Chrome. See crosbug.com/13406.
        tentative_current_input_method_id_.clear();
      }
    }
  }

  // Stops input method daemon based on the |enable_auto_ime_shutdown_| flag
  // and input method configuration being updated.
  // See also: MaybeStartInputMethodDaemon().
  void MaybeStopInputMethodDaemon() {
    // If there is only one input method which is a keyboard layout,
    // and |enable_auto_ime_shutdown_| is true, we'll stop the input
    // method daemon.
    if (ContainOnlyKeyboardLayout(active_input_method_ids_) &&
        enable_auto_ime_shutdown_) {
      if (StopInputMethodDaemon()) {
        // The process is killed. Change the current keyboard layout.
        // We shouldn't use SetCurrentKeyboardLayoutByName() here. See
        // comments at ChangeCurrentInputMethod() for details.
        ChangeCurrentInputMethodFromId(active_input_method_ids_[0]);
      }
    }
  }

  // Change the keyboard layout per input method configuration being
  // updated, if necessary. See also: MaybeStartInputMethodDaemon().
  void MaybeChangeCurrentKeyboardLayout(const std::string& section,
                                        const std::string& config_name,
                                        const ImeConfigValue& value) {
    if (section == language_prefs::kGeneralSectionName &&
        config_name == language_prefs::kPreloadEnginesConfigName) {
      if (value.string_list_value.size() == 1 ||
          (value.string_list_value.size() != 0 &&
           current_input_method_.id().empty())) {
        // This is necessary to initialize current_input_method_. This is also
        // necessary when the current layout (e.g. INTL) out of two (e.g. US and
        // INTL) is disabled.
        ChangeCurrentInputMethodFromId(value.string_list_value[0]);
      }
      DCHECK(!current_input_method_.id().empty());

      // Update the indicator.
      // TODO(yusukes): Remove ActiveInputMethodsChanged notification in
      // FlushImeConfig().
      const size_t num_active_input_methods = GetNumActiveInputMethods();
      FOR_EACH_OBSERVER(InputMethodManager::Observer, observers_,
                        ActiveInputMethodsChanged(this,
                                                  current_input_method(),
                                                  num_active_input_methods));
    }
  }

  // Changes the current input method to |input_method_id| via IBus
  // daemon. If the id is not in the preload_engine list, this function
  // changes the current method to the first preloaded engine. Returns
  // true if the current engine is switched to |input_method_id| or the
  // first one.
  bool ChangeInputMethodViaIBus(const std::string& input_method_id) {
    std::string input_method_id_to_switch = input_method_id;

    if (!InputMethodIsActivated(input_method_id)) {
      // This path might be taken if prefs::kLanguageCurrentInputMethod (NOT
      // synced with cloud) and kLanguagePreloadEngines (synced with cloud) are
      // mismatched. e.g. the former is 'xkb:us::eng' and the latter (on the
      // sync server) is 'xkb:jp::jpn,mozc'.
      scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods());
      DCHECK(!input_methods->empty());
      if (!input_methods->empty()) {
        input_method_id_to_switch = input_methods->at(0).id();
        LOG(INFO) << "Can't change the current input method to "
                  << input_method_id << " since the engine is not preloaded. "
                  << "Switch to " << input_method_id_to_switch << " instead.";
      }
    }

    if (ibus_controller_->ChangeInputMethod(input_method_id_to_switch)) {
      return true;
    }

    // ChangeInputMethod() fails if the IBus daemon is not yet ready.
    LOG(ERROR) << "Can't switch input method to " << input_method_id_to_switch;
    return false;
  }

  // Flushes the input method config data. The config data is queued up in
  // |pending_config_requests_| until the config backend (ibus-memconf)
  // starts.
  void FlushImeConfig() {
    bool active_input_methods_are_changed = false;
    InputMethodConfigRequests::iterator iter = pending_config_requests_.begin();
    while (iter != pending_config_requests_.end()) {
      const std::string& section = iter->first.first;
      const std::string& config_name = iter->first.second;
      ImeConfigValue& value = iter->second;

      if (config_name == language_prefs::kPreloadEnginesConfigName &&
          !tentative_current_input_method_id_.empty()) {
        // We should use |tentative_current_input_method_id_| as the initial
        // active input method for the following reasons:
        //
        // 1) Calls to ChangeInputMethod() will fail if the input method has not
        // yet been added to preload_engines.  As such, the call is deferred
        // until after all config values have been sent to the IME process.
        //
        // 2) We might have already changed the current input method to one
        // of XKB layouts without going through the IBus daemon (we can do
        // it without the IBus daemon started).
        std::vector<std::string>::iterator engine_iter = std::find(
            value.string_list_value.begin(),
            value.string_list_value.end(),
            tentative_current_input_method_id_);
        if (engine_iter != value.string_list_value.end()) {
          // Use std::rotate to keep the relative order of engines the same e.g.
          // from "A,B,C" to "C,A,B".
          // We don't have to |active_input_method_ids_|, which decides the
          // order of engines in the switcher menu, since the relative order
          // of |value.string_list_value| is not changed.
          std::rotate(value.string_list_value.begin(),
                      engine_iter,  // this becomes the new first element
                      value.string_list_value.end());
        } else {
          LOG(WARNING) << tentative_current_input_method_id_
                       << " is not in preload_engines: " << value.ToString();
        }
        tentative_current_input_method_id_.erase();
      }

      if (ibus_controller_->SetImeConfig(section, config_name, value)) {
        // Check if it's a change in active input methods.
        if (config_name == language_prefs::kPreloadEnginesConfigName) {
          active_input_methods_are_changed = true;
          VLOG(1) << "Updated preload_engines: " << value.ToString();
        }
        // Successfully sent. Remove the command and proceed to the next one.
        pending_config_requests_.erase(iter++);
      } else {
        // If SetImeConfig() fails, subsequent calls will likely fail.
        break;
      }
    }

    // Notify the current input method and the number of active input methods to
    // the UI so that the UI could determine e.g. if it should show/hide the
    // input method indicator, etc. We have to call FOR_EACH_OBSERVER here since
    // updating "preload_engine" does not necessarily trigger a DBus signal such
    // as "global-engine-changed". For example,
    // 1) If we change the preload_engine from "xkb:us:intl:eng" (i.e. the
    //    indicator is hidden) to "xkb:us:intl:eng,mozc", we have to update UI
    //    so it shows the indicator, but no signal is sent from ibus-daemon
    //    because the current input method is not changed.
    // 2) If we change the preload_engine from "xkb:us::eng,mozc" (i.e. the
    //    indicator is shown and ibus-daemon is started) to "xkb:us::eng", we
    //    have to update UI so it hides the indicator, but we should not expect
    //    that ibus-daemon could send a DBus signal since the daemon is killed
    //    right after this FlushImeConfig() call.
    if (active_input_methods_are_changed) {
      // The |current_input_method_| member might be stale here as
      // SetImeConfig("preload_engine") call above might change the
      // current input method in ibus-daemon (ex. this occurs when the
      // input method currently in use is removed from the options
      // page). However, it should be safe to use the member here,
      // for the following reasons:
      // 1. If ibus-daemon is to be killed, we'll switch to the only one
      //    keyboard layout, and observers are notified. See
      //    MaybeStopInputMethodDaemon() for details.
      // 2. Otherwise, "global-engine-changed" signal is delivered from
      //    ibus-daemon, and observers are notified. See
      //    InputMethodChangedHandler() for details.
      const size_t num_active_input_methods = GetNumActiveInputMethods();
      FOR_EACH_OBSERVER(InputMethodManager::Observer, observers_,
                        ActiveInputMethodsChanged(this,
                                                  current_input_method_,
                                                  num_active_input_methods));
    }
  }

  // IBusController override.
  virtual void OnCurrentInputMethodChanged(
      const InputMethodDescriptor& current_input_method) {
    // The handler is called when the input method method change is
    // notified via a DBus connection. Since the DBus notificatiosn are
    // handled in the UI thread, we can assume that this function always
    // runs on the UI thread, but just in case.
    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
      LOG(ERROR) << "Not on UI thread";
      return;
    }

    ChangeCurrentInputMethod(current_input_method);
  }

  // IBusController override.
  virtual void OnRegisterImeProperties(
      const ImePropertyList& prop_list) {
    // See comments in InputMethodChangedHandler.
    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
      LOG(ERROR) << "Not on UI thread";
      return;
    }

    RegisterProperties(prop_list);
  }

  // IBusController override.
  virtual void OnUpdateImeProperty(
      const ImePropertyList& prop_list) {
    // See comments in InputMethodChangedHandler.
    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
      LOG(ERROR) << "Not on UI thread";
      return;
    }

    UpdateProperty(prop_list);
  }

  // IBusController override.
  virtual void OnConnectionChange(bool connected) {
    // See comments in InputMethodChangedHandler.
    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
      LOG(ERROR) << "Not on UI thread";
      return;
    }

    ime_connected_ = connected;
    if (connected) {
      pending_config_requests_.clear();
      pending_config_requests_.insert(current_config_values_.begin(),
                                      current_config_values_.end());
      FlushImeConfig();

      ChangeInputMethod(previous_input_method().id());
      ChangeInputMethod(current_input_method().id());<commit_msg>Do not call ChangeInputMethod when IBus connection is established.<commit_after>// Copyright (c) 2012 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/input_method/input_method_manager.h"

#include <algorithm>

#include <glib.h>

#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/singleton.h"
#include "base/message_loop.h"
#include "base/process_util.h"
#include "base/string_split.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/input_method/input_method_util.h"
#include "chrome/browser/chromeos/input_method/virtual_keyboard_selector.h"
#include "chrome/browser/chromeos/input_method/xkeyboard.h"
#include "chrome/browser/chromeos/language_preferences.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "googleurl/src/gurl.h"
#include "ui/base/accelerators/accelerator.h"
#include "unicode/uloc.h"

#if !defined(USE_VIRTUAL_KEYBOARD)
#include "chrome/browser/chromeos/input_method/candidate_window.h"
#endif

#if !defined(USE_AURA)
#include "chrome/browser/chromeos/input_method/hotkey_manager.h"
#include <X11/X.h>  // ShiftMask, ControlMask, etc.
#include <X11/Xutil.h>  // for XK_* macros.
#endif

using content::BrowserThread;

namespace {

const char kIBusDaemonPath[] = "/usr/bin/ibus-daemon";

#if !defined(USE_AURA)
const size_t kMaxInputMethodsPerHotkey = 2;

// For hotkey handling.
enum HotkeyEvent {
  // Global hotkeys.
  kPreviousInputMethod = 0,
  kNextInputMethod,
  // Input method specific hotkeys.
  kJapaneseInputMethod,
  kJapaneseLayout,
  kJapaneseInputMethodOrLayout,
  kKoreanInputMethodOrLayout,
};

// Details of the input method specific hotkeys.
const struct InputMethodSpecificHotkeySetting {
  const char* input_method_ids[kMaxInputMethodsPerHotkey];
  int event_id;
  KeySym keysym;
  uint32 modifiers;
  bool trigger_on_press;  // if true. false means 'trigger on release'.
} kInputMethodSpecificHotkeySettings[] = {
  {
    { "mozc-jp" },
    kJapaneseInputMethod,
    XK_Henkan,
    0x0,
    true,
  },
  {
    { "xkb:jp::jpn" },
    kJapaneseLayout,
    XK_Muhenkan,
    0x0,
    true,
  },
  {
    { "mozc-jp", "xkb:jp::jpn" },
    kJapaneseInputMethodOrLayout,
    XK_Zenkaku_Hankaku,
    0x0,
    true,
  },
  {
    { "mozc-hangul", "xkb:kr:kr104:kor" },
    kKoreanInputMethodOrLayout,
    XK_Hangul,
    0x0,
    true,
  },
  {
    { "mozc-hangul", "xkb:kr:kr104:kor" },
    kKoreanInputMethodOrLayout,
    XK_space,
    ShiftMask,
    true,
  },
};

const size_t kInputMethodSpecificHotkeySettingsLen =
    arraysize(kInputMethodSpecificHotkeySettings);
#endif

// Finds a property which has |new_prop.key| from |prop_list|, and replaces the
// property with |new_prop|. Returns true if such a property is found.
bool FindAndUpdateProperty(
    const chromeos::input_method::ImeProperty& new_prop,
    chromeos::input_method::ImePropertyList* prop_list) {
  for (size_t i = 0; i < prop_list->size(); ++i) {
    chromeos::input_method::ImeProperty& prop = prop_list->at(i);
    if (prop.key == new_prop.key) {
      const int saved_id = prop.selection_item_id;
      // Update the list except the radio id. As written in
      // chromeos_input_method.h, |prop.selection_item_id| is dummy.
      prop = new_prop;
      prop.selection_item_id = saved_id;
      return true;
    }
  }
  return false;
}

}  // namespace

namespace chromeos {
namespace input_method {

// The implementation of InputMethodManager.
class InputMethodManagerImpl
    :
#if !defined(USE_AURA)
      public HotkeyManager::Observer,
#endif
      public InputMethodManager,
      public content::NotificationObserver,
#if !defined(USE_VIRTUAL_KEYBOARD)
      public CandidateWindowController::Observer,
#endif
      public IBusController::Observer {
 public:
  InputMethodManagerImpl()
      : ibus_controller_(IBusController::Create()),
        should_launch_ime_(false),
        ime_connected_(false),
        defer_ime_startup_(false),
        enable_auto_ime_shutdown_(true),
        shutting_down_(false),
        ibus_daemon_process_handle_(base::kNullProcessHandle),
        util_(ibus_controller_->GetSupportedInputMethods()),
        xkeyboard_(XKeyboard::Create(util_)),
        ignore_hotkeys_(false) {
    // Observe APP_TERMINATING to stop input method daemon gracefully.
    // We should not use APP_EXITING here since logout might be canceled by
    // JavaScript after APP_EXITING is sent (crosbug.com/11055).
    // Note that even if we fail to stop input method daemon from
    // Chrome in case of a sudden crash, we have a way to do it from an
    // upstart script. See crosbug.com/6515 and crosbug.com/6995 for
    // details.
    notification_registrar_.Add(this, content::NOTIFICATION_APP_TERMINATING,
                                content::NotificationService::AllSources());

    // The observer should be added before Connect() so we can capture the
    // initial connection change.
    ibus_controller_->AddObserver(this);
    ibus_controller_->Connect();

#if !defined(USE_AURA)
    // Initialize extra_hotkeys_.
    for (size_t i = 0; i < kInputMethodSpecificHotkeySettingsLen; ++i) {
      const char* const* input_method_ids =
          kInputMethodSpecificHotkeySettings[i].input_method_ids;
      for (size_t j = 0;
           j < kMaxInputMethodsPerHotkey && input_method_ids[j];
           ++j) {
        extra_hotkeys_.insert(std::make_pair(
            input_method_ids[j], &kInputMethodSpecificHotkeySettings[i]));
      }
    }
#endif

    EnableHotkeys();

#if !defined(USE_AURA)
    hotkey_manager_.AddObserver(this);
#endif
  }

  virtual ~InputMethodManagerImpl() {
#if !defined(USE_AURA)
    hotkey_manager_.RemoveObserver(this);
#endif
    ibus_controller_->RemoveObserver(this);
#if !defined(USE_VIRTUAL_KEYBOARD)
    if (candidate_window_controller_.get())
      candidate_window_controller_->RemoveObserver(this);
#endif
  }

  virtual void AddObserver(InputMethodManager::Observer* observer) {
    observers_.AddObserver(observer);
  }

  virtual void RemoveObserver(InputMethodManager::Observer* observer) {
    observers_.RemoveObserver(observer);
  }

  virtual void AddCandidateWindowObserver(
      InputMethodManager::CandidateWindowObserver* observer) {
    candidate_window_observers_.AddObserver(observer);
  }

  virtual void RemoveCandidateWindowObserver(
      InputMethodManager::CandidateWindowObserver* observer) {
    candidate_window_observers_.RemoveObserver(observer);
  }

  virtual void AddPreLoginPreferenceObserver(
      InputMethodManager::PreferenceObserver* observer) {
    if (!pre_login_preference_observers_.size()) {
      observer->FirstObserverIsAdded(this);
    }
    pre_login_preference_observers_.AddObserver(observer);
  }

  virtual void RemovePreLoginPreferenceObserver(
      InputMethodManager::PreferenceObserver* observer) {
    pre_login_preference_observers_.RemoveObserver(observer);
  }

  virtual void AddPostLoginPreferenceObserver(
      InputMethodManager::PreferenceObserver* observer) {
    if (!post_login_preference_observers_.size()) {
      observer->FirstObserverIsAdded(this);
    }
    post_login_preference_observers_.AddObserver(observer);
  }

  virtual void RemovePostLoginPreferenceObserver(
      InputMethodManager::PreferenceObserver* observer) {
    post_login_preference_observers_.RemoveObserver(observer);
  }

  virtual void AddVirtualKeyboardObserver(VirtualKeyboardObserver* observer) {
    virtual_keyboard_observers_.AddObserver(observer);
  }

  virtual void RemoveVirtualKeyboardObserver(
      VirtualKeyboardObserver* observer) {
    virtual_keyboard_observers_.RemoveObserver(observer);
  }

  virtual InputMethodDescriptors* GetActiveInputMethods() {
    InputMethodDescriptors* result = new InputMethodDescriptors;
    // Build the active input method descriptors from the active input
    // methods cache |active_input_method_ids_|.
    for (size_t i = 0; i < active_input_method_ids_.size(); ++i) {
      const std::string& input_method_id = active_input_method_ids_[i];
      const InputMethodDescriptor* descriptor =
          util_.GetInputMethodDescriptorFromId(input_method_id);
      if (descriptor) {
        result->push_back(*descriptor);
      } else {
        std::map<std::string, InputMethodDescriptor>::
            const_iterator ix = extra_input_method_ids_.find(input_method_id);
        if (ix != extra_input_method_ids_.end()) {
          result->push_back(ix->second);
        } else {
          LOG(ERROR) << "Descriptor is not found for: " << input_method_id;
        }
      }
    }
    // Initially active_input_method_ids_ is empty. In this case, just
    // returns the fallback input method descriptor.
    if (result->empty()) {
      // Since browser_tests call neither SetImeConfig("preload_engines") nor
      // EnableInputMethod(), this path might be taken.
      result->push_back(
          InputMethodDescriptor::GetFallbackInputMethodDescriptor());
    }
    return result;
  }

  virtual size_t GetNumActiveInputMethods() {
    scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods());
    return input_methods->size();
  }

  virtual InputMethodDescriptors* GetSupportedInputMethods() {
    return ibus_controller_->GetSupportedInputMethods();
  }

  virtual void ChangeInputMethod(const std::string& input_method_id) {
    // Changing the input method isn't guaranteed to succeed here, but we
    // should remember the last one regardless. See comments in
    // FlushImeConfig() for details.
    tentative_current_input_method_id_ = input_method_id;

    if (InputMethodUtil::IsKeyboardLayout(input_method_id)
#if !defined(USE_AURA)
        // TODO(yusukes): Remove this code when R18 stable is shipped.
        // crosbug.com/26245
        && ibus_daemon_process_handle_ == base::kNullProcessHandle
#endif
    ) {
      // We shouldn't use SetCurrentKeyboardLayoutByName() here. See
      // comments at ChangeCurrentInputMethod() for details.
      ChangeCurrentInputMethodFromId(input_method_id);
    } else {
      // Otherwise, start the input method daemon, and change the input
      // method via the daemon.
      StartInputMethodDaemon();
      // ChangeInputMethodViaIBus() fails if the IBus daemon is not
      // ready yet. In this case, we'll defer the input method change
      // until the daemon is ready.
      if (!ChangeInputMethodViaIBus(input_method_id)) {
        VLOG(1) << "Failed to change the input method to " << input_method_id
                << " (deferring)";
      }
    }
  }

  virtual void EnableInputMethods(const std::string& language_code,
                                  InputMethodType type,
                                  const std::string& initial_input_method_id) {
    std::vector<std::string> candidates;
    // Add input methods associated with the language.
    util_.GetInputMethodIdsFromLanguageCode(language_code, type, &candidates);
    // Add the hardware keyboard as well. We should always add this so users
    // can use the hardware keyboard on the login screen and the screen locker.
    candidates.push_back(util_.GetHardwareInputMethodId());

    std::vector<std::string> input_method_ids;
    // First, add the initial input method ID, if it's requested, to
    // input_method_ids, so it appears first on the list of active input
    // methods at the input language status menu.
    if (!initial_input_method_id.empty()) {
      input_method_ids.push_back(initial_input_method_id);
    }

    // Add candidates to input_method_ids, while skipping duplicates.
    for (size_t i = 0; i < candidates.size(); ++i) {
      const std::string& candidate = candidates[i];
      // Not efficient, but should be fine, as the two vectors are very
      // short (2-5 items).
      if (std::count(input_method_ids.begin(), input_method_ids.end(),
                     candidate) == 0) {
        input_method_ids.push_back(candidate);
      }
    }

    // Update ibus-daemon setting. Here, we don't save the input method list
    // in the user's preferences.
    ImeConfigValue value;
    value.type = ImeConfigValue::kValueTypeStringList;
    value.string_list_value = input_method_ids;
    SetImeConfig(language_prefs::kGeneralSectionName,
                 language_prefs::kPreloadEnginesConfigName,
                 value);

    // Finaly, change to the initial input method, as needed.
    if (!initial_input_method_id.empty()) {
      ChangeInputMethod(initial_input_method_id);
    }
  }

  virtual void SetImePropertyActivated(const std::string& key,
                                       bool activated) {
    DCHECK(!key.empty());
    ibus_controller_->SetImePropertyActivated(key, activated);
  }

  virtual bool InputMethodIsActivated(const std::string& input_method_id) {
    scoped_ptr<InputMethodDescriptors> active_input_method_descriptors(
        GetActiveInputMethods());
    for (size_t i = 0; i < active_input_method_descriptors->size(); ++i) {
      if (active_input_method_descriptors->at(i).id() == input_method_id) {
        return true;
      }
    }
    return false;
  }

  virtual bool SetImeConfig(const std::string& section,
                            const std::string& config_name,
                            const ImeConfigValue& value) {
    // If the config change is for preload engines, update the active
    // input methods cache |active_input_method_ids_| here. We need to
    // update the cache before actually flushing the config. since we need
    // to return active input methods from GetActiveInputMethods() before
    // the input method daemon starts. For instance, we need to show the
    // list of available input methods (keyboard layouts) on the login
    // screen before the input method starts.
    if (section == language_prefs::kGeneralSectionName &&
        config_name == language_prefs::kPreloadEnginesConfigName &&
        value.type == ImeConfigValue::kValueTypeStringList) {
      active_input_method_ids_ = value.string_list_value;

      std::map<std::string, InputMethodDescriptor>::const_iterator ix;
      for (ix = extra_input_method_ids_.begin();
           ix != extra_input_method_ids_.end(); ++ix) {
        active_input_method_ids_.push_back(ix->first);
      }
#if !defined(USE_AURA)
      UpdateInputMethodSpecificHotkeys();
#endif
    }

    // Before calling FlushImeConfig(), start input method process if necessary.
    MaybeStartInputMethodDaemon(section, config_name, value);

    const ConfigKeyType key = std::make_pair(section, config_name);
    current_config_values_[key] = value;
    if (ime_connected_) {
      pending_config_requests_[key] = value;
      FlushImeConfig();
    }

    if (section == language_prefs::kGeneralSectionName &&
        config_name == language_prefs::kPreloadEnginesConfigName) {
      // Stop input method process if necessary.
      MaybeStopInputMethodDaemon();
    }
    // Change the current keyboard layout if necessary.
    MaybeChangeCurrentKeyboardLayout(section, config_name, value);
    return pending_config_requests_.empty();
  }

  // TODO(yusukes): Support input method specific hotkeys.
  virtual void AddActiveIme(const std::string& id,
                            const std::string& name,
                            const std::vector<std::string>& layouts,
                            const std::string& language) {
    std::string virtual_layouts = JoinString(layouts, ',');

    extra_input_method_ids_[id] = ibus_controller_->CreateInputMethodDescriptor(
        id, name, virtual_layouts, language);
    active_input_method_ids_.push_back(id);

    // Ensure that the input method daemon is running.
    StartInputMethodDaemon();
  }

  virtual void RemoveActiveIme(const std::string& id) {
    std::vector<std::string>::iterator ix =
        std::find(active_input_method_ids_.begin(),
                  active_input_method_ids_.end(),
                  id);
    if (ix != active_input_method_ids_.end()) {
      active_input_method_ids_.erase(ix);
    }
    extra_input_method_ids_.erase(id);

    // Stop the IME daemon if needed.
    MaybeStopInputMethodDaemon();
  }

  virtual bool GetExtraDescriptor(const std::string& id,
                                  InputMethodDescriptor* descriptor) {
    std::map<std::string, InputMethodDescriptor>::const_iterator ix =
        extra_input_method_ids_.find(id);
    if (ix != extra_input_method_ids_.end()) {
      *descriptor = ix->second;
      return true;
    } else {
      return false;
    }
  }

  virtual InputMethodDescriptor previous_input_method() const {
    if (previous_input_method_.id().empty()) {
      return InputMethodDescriptor::GetFallbackInputMethodDescriptor();
    }
    return previous_input_method_;
  }

  virtual InputMethodDescriptor current_input_method() const {
    if (current_input_method_.id().empty()) {
      return InputMethodDescriptor::GetFallbackInputMethodDescriptor();
    }
    return current_input_method_;
  }

  virtual const ImePropertyList& current_ime_properties() const {
    return current_ime_properties_;
  }

  virtual void SendHandwritingStroke(const HandwritingStroke& stroke) {
    ibus_controller_->SendHandwritingStroke(stroke);
  }

  virtual void CancelHandwritingStrokes(int stroke_count) {
    // TODO(yusukes): Rename the function to CancelHandwritingStrokes.
    ibus_controller_->CancelHandwriting(stroke_count);
  }

  virtual void RegisterVirtualKeyboard(const GURL& launch_url,
                                       const std::string& name,
                                       const std::set<std::string>& layouts,
                                       bool is_system) {
    virtual_keyboard_selector_.AddVirtualKeyboard(launch_url,
                                                  name,
                                                  layouts,
                                                  is_system);
  }

  virtual const std::map<GURL, const VirtualKeyboard*>&
  GetUrlToKeyboardMapping() const {
    return virtual_keyboard_selector_.url_to_keyboard();
  }

  virtual const std::multimap<std::string, const VirtualKeyboard*>&
  GetLayoutNameToKeyboardMapping() const {
    return virtual_keyboard_selector_.layout_to_keyboard();
  }

  virtual bool SetVirtualKeyboardPreference(const std::string& input_method_id,
                                            const GURL& extention_url) {
    const bool result = virtual_keyboard_selector_.SetUserPreference(
        input_method_id, extention_url);
    UpdateVirtualKeyboardUI();
    return result;
  }

  virtual void ClearAllVirtualKeyboardPreferences() {
    virtual_keyboard_selector_.ClearAllUserPreferences();
    UpdateVirtualKeyboardUI();
  }

  virtual InputMethodUtil* GetInputMethodUtil() {
    return &util_;
  }

  virtual XKeyboard* GetXKeyboard() {
    return xkeyboard_.get();
  }

#if !defined(USE_AURA)
  virtual HotkeyManager* GetHotkeyManager() {
    return &hotkey_manager_;
  }
#endif

  virtual void HotkeyPressed(HotkeyManager* manager, int event_id) {
#if !defined(USE_AURA)
    const HotkeyEvent event = HotkeyEvent(event_id);
    switch (event) {
      case kNextInputMethod:
        SwitchToNextInputMethod();
        break;
      case kPreviousInputMethod:
        SwitchToPreviousInputMethod();
        break;
      case kJapaneseInputMethod:
      case kJapaneseLayout:
      case kJapaneseInputMethodOrLayout:
      case kKoreanInputMethodOrLayout:
        SwitchToInputMethod(event);
        break;
    }
#endif
  }

  virtual void CandidateWindowOpened() {
    FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver,
                      candidate_window_observers_,
                      CandidateWindowOpened(this));
  }

  virtual void CandidateWindowClosed() {
    FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver,
                      candidate_window_observers_,
                      CandidateWindowClosed(this));
  }

  virtual bool SwitchToNextInputMethod() {
    if (ignore_hotkeys_) {
      return false;
    }

    // Sanity checks.
    if (active_input_method_ids_.empty()) {
      LOG(ERROR) << "active input method is empty";
      return false;
    }
    if (current_input_method_.id().empty()) {
      LOG(ERROR) << "current_input_method_ is unknown";
      return false;
    }

    // Find the next input method.
    std::vector<std::string>::const_iterator iter =
        std::find(active_input_method_ids_.begin(),
                  active_input_method_ids_.end(),
                  current_input_method_.id());
    if (iter != active_input_method_ids_.end()) {
      ++iter;
    }
    if (iter == active_input_method_ids_.end()) {
      iter = active_input_method_ids_.begin();
    }
    ChangeInputMethod(*iter);
    return true;
  }

  virtual bool SwitchToPreviousInputMethod() {
    if (ignore_hotkeys_) {
      return false;
    }

    // Sanity check.
    if (active_input_method_ids_.empty()) {
      LOG(ERROR) << "active input method is empty";
      return false;
    }

    if (previous_input_method_.id().empty() ||
        previous_input_method_.id() == current_input_method_.id()) {
      return SwitchToNextInputMethod();
    }

    std::vector<std::string>::const_iterator iter =
        std::find(active_input_method_ids_.begin(),
                  active_input_method_ids_.end(),
                  previous_input_method_.id());
    if (iter == active_input_method_ids_.end()) {
      // previous_input_method_ is not supported.
      return SwitchToNextInputMethod();
    }
    ChangeInputMethod(*iter);
    return true;
  }

  virtual bool SwitchInputMethod(const ui::Accelerator& accelerator) {
    if (ignore_hotkeys_) {
      return false;
    }

    // Sanity check.
    if (active_input_method_ids_.empty()) {
      LOG(ERROR) << "active input method is empty";
      return false;
    }

    // Get the list of input method ids for the |accelerator|. For example, get
    // { "mozc-hangul", "xkb:kr:kr104:kor" } for ui::VKEY_DBE_SBCSCHAR.
    std::vector<std::string> input_method_ids_to_switch;
    switch (accelerator.key_code()) {
      case ui::VKEY_CONVERT:  // Henkan key on JP106 keyboard
        input_method_ids_to_switch.push_back("mozc-jp");
        break;
      case ui::VKEY_NONCONVERT:  // Muhenkan key on JP106 keyboard
        input_method_ids_to_switch.push_back("xkb:jp::jpn");
        break;
      case ui::VKEY_DBE_SBCSCHAR:  // ZenkakuHankaku key on JP106 keyboard
      case ui::VKEY_DBE_DBCSCHAR:
        input_method_ids_to_switch.push_back("mozc-jp");
        input_method_ids_to_switch.push_back("xkb:jp::jpn");
        break;
      case ui::VKEY_HANGUL:
        input_method_ids_to_switch.push_back("mozc-hangul");
        break;
      case ui::VKEY_SPACE:  // Shift+Space
        input_method_ids_to_switch.push_back("mozc-hangul");
        input_method_ids_to_switch.push_back("xkb:kr:kr104:kor");
        break;
      default:
        NOTREACHED();
        break;
    }
    if (input_method_ids_to_switch.empty()) {
      LOG(ERROR) << "Unexpected VKEY: " << accelerator.key_code();
      return false;
    }

    // Obtain the intersection of input_method_ids_to_switch and
    // active_input_method_ids_. The order of IDs in active_input_method_ids_ is
    // preserved.
    std::vector<std::string> ids;
    for (size_t i = 0; i < input_method_ids_to_switch.size(); ++i) {
      const std::string& id = input_method_ids_to_switch[i];
      if (std::find(active_input_method_ids_.begin(),
                    active_input_method_ids_.end(),
                    id) != active_input_method_ids_.end()) {
        ids.push_back(id);
      }
    }
    if (ids.empty()) {
      // No input method for the accelerator is active. For example, we should
      // just ignore VKEY_HANGUL when mozc-hangul is not active.
      return false;
    }

    // If |current_input_method_| is not in ids, switch to ids[0]. If
    // |current_input_method_| is ids[N], switch to ids[N+1].
    std::vector<std::string>::const_iterator iter =
        std::find(ids.begin(), ids.end(), current_input_method_.id());
    if (iter != ids.end()) {
      ++iter;
    }
    if (iter == ids.end()) {
      iter = ids.begin();
    }
    ChangeInputMethod(*iter);
    return true;  // consume the accelerator.
  }

  virtual void EnableHotkeys() {
#if defined(USE_AURA)
    ignore_hotkeys_ = false;
#else
    // Add Ctrl+space.
    hotkey_manager_.AddHotkey(kPreviousInputMethod,
                              XK_space,
                              ControlMask,
                              true /* trigger on key press */);

    // Add Alt+Shift. For this hotkey, we use "trigger on key release" so that
    // the current IME is not changed on pressing Alt+Shift+something.
    hotkey_manager_.AddHotkey(kNextInputMethod,
                              XK_Shift_L,
                              ShiftMask | Mod1Mask,
                              false /* trigger on key release. */);
    hotkey_manager_.AddHotkey(kNextInputMethod,
                              XK_Shift_R,
                              ShiftMask | Mod1Mask,
                              false);
    hotkey_manager_.AddHotkey(kNextInputMethod,
                              XK_Meta_L,
                              ShiftMask | Mod1Mask,
                              false);
    hotkey_manager_.AddHotkey(kNextInputMethod,
                              XK_Meta_R,
                              ShiftMask | Mod1Mask,
                              false);
    // Input method specific hotkeys will be added in SetImeConfig().
#endif
  }

  virtual void DisableHotkeys() {
#if defined(USE_AURA)
    ignore_hotkeys_ = true;
#else
    hotkey_manager_.RemoveHotkey(kPreviousInputMethod);
    hotkey_manager_.RemoveHotkey(kNextInputMethod);
#endif
  }

  static InputMethodManagerImpl* GetInstance() {
    return Singleton<InputMethodManagerImpl,
        DefaultSingletonTraits<InputMethodManagerImpl> >::get();
  }

 private:
  friend struct DefaultSingletonTraits<InputMethodManagerImpl>;

  // Returns true if the given input method config value is a string list
  // that only contains an input method ID of a keyboard layout.
  bool ContainOnlyKeyboardLayout(const std::vector<std::string>& value) {
    for (size_t i = 0; i < value.size(); ++i) {
      if (!InputMethodUtil::IsKeyboardLayout(value[i])) {
        return false;
      }
    }
    return true;
  }

  // Starts input method daemon based on the |defer_ime_startup_| flag and
  // input method configuration being updated. |section| is a section name of
  // the input method configuration (e.g. "general", "general/hotkey").
  // |config_name| is a name of the configuration (e.g. "preload_engines",
  // "previous_engine"). |value| is the configuration value to be set.
  void MaybeStartInputMethodDaemon(const std::string& section,
                                   const std::string& config_name,
                                   const ImeConfigValue& value) {
    if (section == language_prefs::kGeneralSectionName &&
        config_name == language_prefs::kPreloadEnginesConfigName &&
        value.type == ImeConfigValue::kValueTypeStringList &&
        !value.string_list_value.empty()) {
      // If there is only one input method which is a keyboard layout,
      // we don't start the input method processes.  When
      // |defer_ime_startup_| is true, we don't start it either.
      if (ContainOnlyKeyboardLayout(value.string_list_value) ||
          defer_ime_startup_) {
        // Do not start the input method daemon.
        return;
      }

      // Otherwise, start the input method daemon.
      const bool just_started = StartInputMethodDaemon();
      if (!just_started) {
        // The daemon is already running.
        // Do not |update tentative_current_input_method_id_|.
        return;
      }

      // The daemon has just been started. To select the initial input method
      // engine correctly, update |tentative_current_input_method_id_|.
      if (tentative_current_input_method_id_.empty()) {
        // Since the |current_input_method_| is in the preloaded engine list,
        // switch to the engine. This is necessary ex. for the following case:
        // 1. "xkb:jp::jpn" is enabled. ibus-daemon is not running.
        // 2. A user enabled "mozc" via DOMUI as well. ibus-daemon is started
        //    and the preloaded engine list is set to "mozc,xkb:jp::jpn".
        // 3. ibus-daemon selects "mozc" as its current engine since "mozc" is
        //    on top of the preloaded engine list.
        // 4. Therefore, we have to change the current engine to "xkb:jp::jpn"
        //    explicitly to avoid unexpected engine switch.
        tentative_current_input_method_id_ = current_input_method_.id();
      }

      if (std::find(value.string_list_value.begin(),
                    value.string_list_value.end(),
                    tentative_current_input_method_id_)
          == value.string_list_value.end()) {
        // The |current_input_method_| is NOT in the preloaded engine list.
        // In this case, ibus-daemon will automatically select the first engine
        // in the list, |value.string_list_value[0]|, and send global engine
        // changed signal to Chrome. See crosbug.com/13406.
        tentative_current_input_method_id_.clear();
      }
    }
  }

  // Stops input method daemon based on the |enable_auto_ime_shutdown_| flag
  // and input method configuration being updated.
  // See also: MaybeStartInputMethodDaemon().
  void MaybeStopInputMethodDaemon() {
    // If there is only one input method which is a keyboard layout,
    // and |enable_auto_ime_shutdown_| is true, we'll stop the input
    // method daemon.
    if (ContainOnlyKeyboardLayout(active_input_method_ids_) &&
        enable_auto_ime_shutdown_) {
      if (StopInputMethodDaemon()) {
        // The process is killed. Change the current keyboard layout.
        // We shouldn't use SetCurrentKeyboardLayoutByName() here. See
        // comments at ChangeCurrentInputMethod() for details.
        ChangeCurrentInputMethodFromId(active_input_method_ids_[0]);
      }
    }
  }

  // Change the keyboard layout per input method configuration being
  // updated, if necessary. See also: MaybeStartInputMethodDaemon().
  void MaybeChangeCurrentKeyboardLayout(const std::string& section,
                                        const std::string& config_name,
                                        const ImeConfigValue& value) {
    if (section == language_prefs::kGeneralSectionName &&
        config_name == language_prefs::kPreloadEnginesConfigName) {
      if (value.string_list_value.size() == 1 ||
          (value.string_list_value.size() != 0 &&
           current_input_method_.id().empty())) {
        // This is necessary to initialize current_input_method_. This is also
        // necessary when the current layout (e.g. INTL) out of two (e.g. US and
        // INTL) is disabled.
        ChangeCurrentInputMethodFromId(value.string_list_value[0]);
      }
      DCHECK(!current_input_method_.id().empty());

      // Update the indicator.
      // TODO(yusukes): Remove ActiveInputMethodsChanged notification in
      // FlushImeConfig().
      const size_t num_active_input_methods = GetNumActiveInputMethods();
      FOR_EACH_OBSERVER(InputMethodManager::Observer, observers_,
                        ActiveInputMethodsChanged(this,
                                                  current_input_method(),
                                                  num_active_input_methods));
    }
  }

  // Changes the current input method to |input_method_id| via IBus
  // daemon. If the id is not in the preload_engine list, this function
  // changes the current method to the first preloaded engine. Returns
  // true if the current engine is switched to |input_method_id| or the
  // first one.
  bool ChangeInputMethodViaIBus(const std::string& input_method_id) {
    std::string input_method_id_to_switch = input_method_id;

    if (!InputMethodIsActivated(input_method_id)) {
      // This path might be taken if prefs::kLanguageCurrentInputMethod (NOT
      // synced with cloud) and kLanguagePreloadEngines (synced with cloud) are
      // mismatched. e.g. the former is 'xkb:us::eng' and the latter (on the
      // sync server) is 'xkb:jp::jpn,mozc'.
      scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods());
      DCHECK(!input_methods->empty());
      if (!input_methods->empty()) {
        input_method_id_to_switch = input_methods->at(0).id();
        LOG(INFO) << "Can't change the current input method to "
                  << input_method_id << " since the engine is not preloaded. "
                  << "Switch to " << input_method_id_to_switch << " instead.";
      }
    }

    if (ibus_controller_->ChangeInputMethod(input_method_id_to_switch)) {
      return true;
    }

    // ChangeInputMethod() fails if the IBus daemon is not yet ready.
    LOG(ERROR) << "Can't switch input method to " << input_method_id_to_switch;
    return false;
  }

  // Flushes the input method config data. The config data is queued up in
  // |pending_config_requests_| until the config backend (ibus-memconf)
  // starts.
  void FlushImeConfig() {
    bool active_input_methods_are_changed = false;
    InputMethodConfigRequests::iterator iter = pending_config_requests_.begin();
    while (iter != pending_config_requests_.end()) {
      const std::string& section = iter->first.first;
      const std::string& config_name = iter->first.second;
      ImeConfigValue& value = iter->second;

      if (config_name == language_prefs::kPreloadEnginesConfigName &&
          !tentative_current_input_method_id_.empty()) {
        // We should use |tentative_current_input_method_id_| as the initial
        // active input method for the following reasons:
        //
        // 1) Calls to ChangeInputMethod() will fail if the input method has not
        // yet been added to preload_engines.  As such, the call is deferred
        // until after all config values have been sent to the IME process.
        //
        // 2) We might have already changed the current input method to one
        // of XKB layouts without going through the IBus daemon (we can do
        // it without the IBus daemon started).
        std::vector<std::string>::iterator engine_iter = std::find(
            value.string_list_value.begin(),
            value.string_list_value.end(),
            tentative_current_input_method_id_);
        if (engine_iter != value.string_list_value.end()) {
          // Use std::rotate to keep the relative order of engines the same e.g.
          // from "A,B,C" to "C,A,B".
          // We don't have to |active_input_method_ids_|, which decides the
          // order of engines in the switcher menu, since the relative order
          // of |value.string_list_value| is not changed.
          std::rotate(value.string_list_value.begin(),
                      engine_iter,  // this becomes the new first element
                      value.string_list_value.end());
        } else {
          LOG(WARNING) << tentative_current_input_method_id_
                       << " is not in preload_engines: " << value.ToString();
        }
        tentative_current_input_method_id_.erase();
      }

      if (ibus_controller_->SetImeConfig(section, config_name, value)) {
        // Check if it's a change in active input methods.
        if (config_name == language_prefs::kPreloadEnginesConfigName) {
          active_input_methods_are_changed = true;
          VLOG(1) << "Updated preload_engines: " << value.ToString();
        }
        // Successfully sent. Remove the command and proceed to the next one.
        pending_config_requests_.erase(iter++);
      } else {
        // If SetImeConfig() fails, subsequent calls will likely fail.
        break;
      }
    }

    // Notify the current input method and the number of active input methods to
    // the UI so that the UI could determine e.g. if it should show/hide the
    // input method indicator, etc. We have to call FOR_EACH_OBSERVER here since
    // updating "preload_engine" does not necessarily trigger a DBus signal such
    // as "global-engine-changed". For example,
    // 1) If we change the preload_engine from "xkb:us:intl:eng" (i.e. the
    //    indicator is hidden) to "xkb:us:intl:eng,mozc", we have to update UI
    //    so it shows the indicator, but no signal is sent from ibus-daemon
    //    because the current input method is not changed.
    // 2) If we change the preload_engine from "xkb:us::eng,mozc" (i.e. the
    //    indicator is shown and ibus-daemon is started) to "xkb:us::eng", we
    //    have to update UI so it hides the indicator, but we should not expect
    //    that ibus-daemon could send a DBus signal since the daemon is killed
    //    right after this FlushImeConfig() call.
    if (active_input_methods_are_changed) {
      // The |current_input_method_| member might be stale here as
      // SetImeConfig("preload_engine") call above might change the
      // current input method in ibus-daemon (ex. this occurs when the
      // input method currently in use is removed from the options
      // page). However, it should be safe to use the member here,
      // for the following reasons:
      // 1. If ibus-daemon is to be killed, we'll switch to the only one
      //    keyboard layout, and observers are notified. See
      //    MaybeStopInputMethodDaemon() for details.
      // 2. Otherwise, "global-engine-changed" signal is delivered from
      //    ibus-daemon, and observers are notified. See
      //    InputMethodChangedHandler() for details.
      const size_t num_active_input_methods = GetNumActiveInputMethods();
      FOR_EACH_OBSERVER(InputMethodManager::Observer, observers_,
                        ActiveInputMethodsChanged(this,
                                                  current_input_method_,
                                                  num_active_input_methods));
    }
  }

  // IBusController override.
  virtual void OnCurrentInputMethodChanged(
      const InputMethodDescriptor& current_input_method) {
    // The handler is called when the input method method change is
    // notified via a DBus connection. Since the DBus notificatiosn are
    // handled in the UI thread, we can assume that this function always
    // runs on the UI thread, but just in case.
    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
      LOG(ERROR) << "Not on UI thread";
      return;
    }

    ChangeCurrentInputMethod(current_input_method);
  }

  // IBusController override.
  virtual void OnRegisterImeProperties(
      const ImePropertyList& prop_list) {
    // See comments in InputMethodChangedHandler.
    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
      LOG(ERROR) << "Not on UI thread";
      return;
    }

    RegisterProperties(prop_list);
  }

  // IBusController override.
  virtual void OnUpdateImeProperty(
      const ImePropertyList& prop_list) {
    // See comments in InputMethodChangedHandler.
    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
      LOG(ERROR) << "Not on UI thread";
      return;
    }

    UpdateProperty(prop_list);
  }

  // IBusController override.
  virtual void OnConnectionChange(bool connected) {
    // See comments in InputMethodChangedHandler.
    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
      LOG(ERROR) << "Not on UI thread";
      return;
    }

    ime_connected_ = connected;
    if (connected) {
      pending_config_requests_.clear();
      pending_config_requests_.insert(current_config_values_.begin(),
                                      current_config_values_.end());
      FlushImeConfig();
{'fraction_non_alphanumeric': 0.09414973781558658, 'fraction_numerical': 0.0025159739434777737, 'mean_word_length': 2.854625762158529, 'pattern_counts': {'":': 0, '<': 185, '<?xml version=': 0, '>': 121, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 60, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2012599', 'n_tokens_mistral': 23417, 'n_tokens_neox': 22895, 'n_words': 5719}