<commit_before>// Copyright (c) 2010 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/cros/input_method_library.h"

#include <glib.h>
#include <signal.h>

#include "unicode/uloc.h"

#include "base/basictypes.h"
#include "base/message_loop.h"
#include "base/string_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_thread.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/cros/keyboard_library.h"
#include "chrome/browser/chromeos/input_method/input_method_util.h"
#include "chrome/browser/chromeos/language_preferences.h"
#include "chrome/common/notification_observer.h"
#include "chrome/common/notification_registrar.h"
#include "chrome/common/notification_service.h"

namespace {
const char kIBusDaemonPath[] = "/usr/bin/ibus-daemon";
const char kCandidateWindowPath[] = "/opt/google/chrome/candidate_window";

// 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::ImeProperty& new_prop,
                           chromeos::ImePropertyList* prop_list) {
  for (size_t i = 0; i < prop_list->size(); ++i) {
    chromeos::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 {

class InputMethodLibraryImpl : public InputMethodLibrary,
                               public NotificationObserver {
 public:
  InputMethodLibraryImpl()
      : input_method_status_connection_(NULL),
        previous_input_method_("", "", "", ""),
        current_input_method_("", "", "", ""),
        should_launch_ime_(false),
        ime_connected_(false),
        defer_ime_startup_(false),
        should_change_input_method_(false),
        ibus_daemon_process_id_(0),
        candidate_window_process_id_(0),
        failure_count_(0) {
    scoped_ptr<InputMethodDescriptors> input_method_descriptors(
        CreateFallbackInputMethodDescriptors());
    current_input_method_ = input_method_descriptors->at(0);
    if (CrosLibrary::Get()->EnsureLoaded()) {
      current_input_method_id_ = chromeos::GetHardwareKeyboardLayoutName();
    }
    // Observe APP_EXITING to stop input method processes gracefully.
    // Note that even if we fail to stop input method processes 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, NotificationType::APP_EXITING,
                                NotificationService::AllSources());
  }

  ~InputMethodLibraryImpl() {
  }

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

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

  InputMethodDescriptors* GetActiveInputMethods() {
    chromeos::InputMethodDescriptors* result = NULL;
    // The connection does not need to be alive, but it does need to be created.
    if (EnsureLoadedAndStarted()) {
      result = chromeos::GetActiveInputMethods(input_method_status_connection_);
    }
    if (!result || result->empty()) {
      result = CreateFallbackInputMethodDescriptors();
    }
    return result;
  }

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

  InputMethodDescriptors* GetSupportedInputMethods() {
    InputMethodDescriptors* result = NULL;
    // The connection does not need to be alive, but it does need to be created.
    if (EnsureLoadedAndStarted()) {
      result = chromeos::GetSupportedInputMethods(
          input_method_status_connection_);
    }
    if (!result || result->empty()) {
      result = CreateFallbackInputMethodDescriptors();
    }
    return result;
  }

  void ChangeInputMethod(const std::string& input_method_id) {
    current_input_method_id_ = input_method_id;
    if (EnsureLoadedAndStarted()) {
      if (input_method_id != chromeos::GetHardwareKeyboardLayoutName()) {
        StartInputMethodProcesses();
      }
      chromeos::ChangeInputMethod(
          input_method_status_connection_, input_method_id.c_str());
    }
  }

  void SetImePropertyActivated(const std::string& key, bool activated) {
    DCHECK(!key.empty());
    if (EnsureLoadedAndStarted()) {
      chromeos::SetImePropertyActivated(
          input_method_status_connection_, key.c_str(), activated);
    }
  }

  bool InputMethodIsActivated(const std::string& input_method_id) {
    scoped_ptr<InputMethodDescriptors> active_input_method_descriptors(
        CrosLibrary::Get()->GetInputMethodLibrary()->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;
  }

  bool GetImeConfig(const char* section, const char* config_name,
                    ImeConfigValue* out_value) {
    bool success = false;
    if (EnsureLoadedAndStarted()) {
      success = chromeos::GetImeConfig(input_method_status_connection_,
                                       section, config_name, out_value);
    }
    return success;
  }

  bool SetImeConfig(const char* section, const char* config_name,
                    const ImeConfigValue& value) {
    MaybeStartOrStopInputMethodProcesses(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();
    }
    return pending_config_requests_.empty();
  }

  virtual const InputMethodDescriptor& previous_input_method() const {
    return previous_input_method_;
  }
  virtual const InputMethodDescriptor& current_input_method() const {
    return current_input_method_;
  }

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

 private:
  // Starts or stops the input method processes based on the current state.
  void MaybeStartOrStopInputMethodProcesses(
      const char* section,
      const char* config_name,
      const ImeConfigValue& value) {
    if (!strcmp(language_prefs::kGeneralSectionName, section) &&
        !strcmp(language_prefs::kPreloadEnginesConfigName, config_name)) {
      if (EnsureLoadedAndStarted()) {
        // If there are no input methods other than one for the hardware
        // keyboard, we'll stop the input method processes.
        if (value.type == ImeConfigValue::kValueTypeStringList &&
            value.string_list_value.size() == 1 &&
            value.string_list_value[0] ==
            chromeos::GetHardwareKeyboardLayoutName()) {
          StopInputMethodProcesses();
        } else if (!defer_ime_startup_) {
          StartInputMethodProcesses();
        }
        chromeos::SetActiveInputMethods(input_method_status_connection_, value);
      }
    }
  }

  // Flushes the input method config data. The config data is queued up in
  // |pending_config_requests_| until the config backend (ibus-memconf)
  // starts. Since there is no good way to get notified when the config
  // backend starts, we use a timer to periodically attempt to send the
  // config data to the config backend.
  void FlushImeConfig() {
    bool active_input_methods_are_changed = false;
    bool completed = false;
    if (EnsureLoadedAndStarted()) {
      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;
        const ImeConfigValue& value = iter->second;
        if (chromeos::SetImeConfig(input_method_status_connection_,
                                   section.c_str(),
                                   config_name.c_str(),
                                   value)) {
          // Check if it's a change in active input methods.
          if (config_name == language_prefs::kPreloadEnginesConfigName) {
            active_input_methods_are_changed = true;
          }
          // 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;
        }
      }
      if (pending_config_requests_.empty()) {
        // 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.
        if (should_change_input_method_) {
          if (chromeos::ChangeInputMethod(input_method_status_connection_,
                                          current_input_method_id_.c_str())) {
            should_change_input_method_ = false;
            completed = true;
            active_input_methods_are_changed = true;
          }
        } else {
          completed = true;
        }
      }
    }

    if (completed) {
      timer_.Stop();  // no-op if it's not running.
    } else {
      // Flush is not completed. Start a timer if it's not yet running.
      if (!timer_.IsRunning()) {
        static const int64 kTimerIntervalInMsec = 100;
        failure_count_ = 0;
        timer_.Start(base::TimeDelta::FromMilliseconds(kTimerIntervalInMsec),
                     this, &InputMethodLibraryImpl::FlushImeConfig);
      } else {
        // The timer is already running. We'll give up if it reaches the
        // max retry count.
        static const int kMaxRetries = 15;
        ++failure_count_;
        if (failure_count_ > kMaxRetries) {
          LOG(ERROR) << "FlushImeConfig: Max retries exceeded. "
                     << "current_input_method_id: " << current_input_method_id_
                     << " pending_config_requests.size: "
                     << pending_config_requests_.size();
          timer_.Stop();
        }
      }
    }<commit_msg>Change the way to change the current input method in FlushImeConfig() so that the flush function does not call chromeos::ChangeInputMethod() too often.<commit_after>// Copyright (c) 2010 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/cros/input_method_library.h"

#include <glib.h>
#include <signal.h>

#include "unicode/uloc.h"

#include "base/basictypes.h"
#include "base/message_loop.h"
#include "base/string_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_thread.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/cros/keyboard_library.h"
#include "chrome/browser/chromeos/input_method/input_method_util.h"
#include "chrome/browser/chromeos/language_preferences.h"
#include "chrome/common/notification_observer.h"
#include "chrome/common/notification_registrar.h"
#include "chrome/common/notification_service.h"

namespace {
const char kIBusDaemonPath[] = "/usr/bin/ibus-daemon";
const char kCandidateWindowPath[] = "/opt/google/chrome/candidate_window";

// 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::ImeProperty& new_prop,
                           chromeos::ImePropertyList* prop_list) {
  for (size_t i = 0; i < prop_list->size(); ++i) {
    chromeos::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 {

class InputMethodLibraryImpl : public InputMethodLibrary,
                               public NotificationObserver {
 public:
  InputMethodLibraryImpl()
      : input_method_status_connection_(NULL),
        previous_input_method_("", "", "", ""),
        current_input_method_("", "", "", ""),
        should_launch_ime_(false),
        ime_connected_(false),
        defer_ime_startup_(false),
        should_change_input_method_(false),
        ibus_daemon_process_id_(0),
        candidate_window_process_id_(0),
        failure_count_(0) {
    scoped_ptr<InputMethodDescriptors> input_method_descriptors(
        CreateFallbackInputMethodDescriptors());
    current_input_method_ = input_method_descriptors->at(0);
    if (CrosLibrary::Get()->EnsureLoaded()) {
      current_input_method_id_ = chromeos::GetHardwareKeyboardLayoutName();
    }
    // Observe APP_EXITING to stop input method processes gracefully.
    // Note that even if we fail to stop input method processes 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, NotificationType::APP_EXITING,
                                NotificationService::AllSources());
  }

  ~InputMethodLibraryImpl() {
  }

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

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

  InputMethodDescriptors* GetActiveInputMethods() {
    chromeos::InputMethodDescriptors* result = NULL;
    // The connection does not need to be alive, but it does need to be created.
    if (EnsureLoadedAndStarted()) {
      result = chromeos::GetActiveInputMethods(input_method_status_connection_);
    }
    if (!result || result->empty()) {
      result = CreateFallbackInputMethodDescriptors();
    }
    return result;
  }

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

  InputMethodDescriptors* GetSupportedInputMethods() {
    InputMethodDescriptors* result = NULL;
    // The connection does not need to be alive, but it does need to be created.
    if (EnsureLoadedAndStarted()) {
      result = chromeos::GetSupportedInputMethods(
          input_method_status_connection_);
    }
    if (!result || result->empty()) {
      result = CreateFallbackInputMethodDescriptors();
    }
    return result;
  }

  void ChangeInputMethod(const std::string& input_method_id) {
    current_input_method_id_ = input_method_id;
    if (EnsureLoadedAndStarted()) {
      if (input_method_id != chromeos::GetHardwareKeyboardLayoutName()) {
        StartInputMethodProcesses();
      }
      ChangeInputMethodInternal(input_method_id);
    }
  }

  void SetImePropertyActivated(const std::string& key, bool activated) {
    DCHECK(!key.empty());
    if (EnsureLoadedAndStarted()) {
      chromeos::SetImePropertyActivated(
          input_method_status_connection_, key.c_str(), activated);
    }
  }

  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;
  }

  bool GetImeConfig(const char* section, const char* config_name,
                    ImeConfigValue* out_value) {
    bool success = false;
    if (EnsureLoadedAndStarted()) {
      success = chromeos::GetImeConfig(input_method_status_connection_,
                                       section, config_name, out_value);
    }
    return success;
  }

  bool SetImeConfig(const char* section, const char* config_name,
                    const ImeConfigValue& value) {
    MaybeStartOrStopInputMethodProcesses(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();
    }
    return pending_config_requests_.empty();
  }

  virtual const InputMethodDescriptor& previous_input_method() const {
    return previous_input_method_;
  }
  virtual const InputMethodDescriptor& current_input_method() const {
    return current_input_method_;
  }

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

 private:
  // Starts or stops the input method processes based on the current state.
  void MaybeStartOrStopInputMethodProcesses(
      const char* section,
      const char* config_name,
      const ImeConfigValue& value) {
    if (!strcmp(language_prefs::kGeneralSectionName, section) &&
        !strcmp(language_prefs::kPreloadEnginesConfigName, config_name)) {
      if (EnsureLoadedAndStarted()) {
        // If there are no input methods other than one for the hardware
        // keyboard, we'll stop the input method processes.
        if (value.type == ImeConfigValue::kValueTypeStringList &&
            value.string_list_value.size() == 1 &&
            value.string_list_value[0] ==
            chromeos::GetHardwareKeyboardLayoutName()) {
          StopInputMethodProcesses();
        } else if (!defer_ime_startup_) {
          StartInputMethodProcesses();
        }
        chromeos::SetActiveInputMethods(input_method_status_connection_, value);
      }
    }
  }

  // Changes the current input method to |input_method_id|. 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 ChangeInputMethodInternal(const std::string& input_method_id) {
    DCHECK(EnsureLoadedAndStarted());
    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 (chromeos::ChangeInputMethod(input_method_status_connection_,
                                    input_method_id_to_switch.c_str())) {
      return true;
    }

    // Not reached.
    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. Since there is no good way to get notified when the config
  // backend starts, we use a timer to periodically attempt to send the
  // config data to the config backend.
  void FlushImeConfig() {
    bool active_input_methods_are_changed = false;
    if (EnsureLoadedAndStarted()) {
      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;
        const ImeConfigValue& value = iter->second;
        if (chromeos::SetImeConfig(input_method_status_connection_,
                                   section.c_str(),
                                   config_name.c_str(),
                                   value)) {
          // Check if it's a change in active input methods.
          if (config_name == language_prefs::kPreloadEnginesConfigName) {
            active_input_methods_are_changed = true;
          }
          // 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;
        }
      }
      if (pending_config_requests_.empty()) {
        // 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.
        if (should_change_input_method_) {
          ChangeInputMethodInternal(current_input_method_id_);
          should_change_input_method_ = false;
          active_input_methods_are_changed = true;
        }
      }
    }

    if (pending_config_requests_.empty()) {
      timer_.Stop();  // no-op if it's not running.
    } else {
      // Flush is not completed. Start a timer if it's not yet running.
      if (!timer_.IsRunning()) {
        static const int64 kTimerIntervalInMsec = 100;
        failure_count_ = 0;
        timer_.Start(base::TimeDelta::FromMilliseconds(kTimerIntervalInMsec),
                     this, &InputMethodLibraryImpl::FlushImeConfig);
      } else {
        // The timer is already running. We'll give up if it reaches the
        // max retry count.
        static const int kMaxRetries = 15;
        ++failure_count_;
        if (failure_count_ > kMaxRetries) {
          LOG(ERROR) << "FlushImeConfig: Max retries exceeded. "
                     << "current_input_method_id: " << current_input_method_id_
                     << " pending_config_requests.size: "
                     << pending_config_requests_.size();
          timer_.Stop();
        }
      }
    }
{'fraction_non_alphanumeric': 0.09520645046960836, 'fraction_numerical': 0.0025252525252525255, 'mean_word_length': 2.97551954913702, 'pattern_counts': {'":': 0, '<': 54, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 11, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1785581', 'n_tokens_mistral': 6533, 'n_tokens_neox': 6416, 'n_words': 1556}