| // 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_impl.h" |
| |
| #include <algorithm> // std::find |
| |
| #include <sstream> |
| |
| #include "ash/ime/input_method_menu_item.h" |
| #include "ash/ime/input_method_menu_manager.h" |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/metrics/histogram.h" |
| #include "base/metrics/sparse_histogram.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/sys_info.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chromeos/input_method/candidate_window_controller.h" |
| #include "chrome/browser/chromeos/input_method/component_extension_ime_manager_impl.h" |
| #include "chrome/browser/chromeos/input_method/input_method_engine.h" |
| #include "chrome/browser/chromeos/language_preferences.h" |
| #include "chrome/browser/chromeos/login/session/user_session_manager.h" |
| #include "chrome/browser/chromeos/profiles/profile_helper.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/common/pref_names.h" |
| #include "chromeos/ime/component_extension_ime_manager.h" |
| #include "chromeos/ime/extension_ime_util.h" |
| #include "chromeos/ime/fake_ime_keyboard.h" |
| #include "chromeos/ime/ime_keyboard.h" |
| #include "chromeos/ime/input_method_delegate.h" |
| #include "components/user_manager/user_manager.h" |
| #include "third_party/icu/source/common/unicode/uloc.h" |
| #include "ui/base/accelerators/accelerator.h" |
| #include "ui/keyboard/keyboard_controller.h" |
| #include "ui/keyboard/keyboard_util.h" |
| |
| namespace chromeos { |
| namespace input_method { |
| |
| namespace { |
| |
| bool Contains(const std::vector<std::string>& container, |
| const std::string& value) { |
| return std::find(container.begin(), container.end(), value) != |
| container.end(); |
| } |
| |
| enum InputMethodCategory { |
| INPUT_METHOD_CATEGORY_UNKNOWN = 0, |
| INPUT_METHOD_CATEGORY_XKB, // XKB input methods |
| INPUT_METHOD_CATEGORY_ZH, // Chinese input methods |
| INPUT_METHOD_CATEGORY_JA, // Japanese input methods |
| INPUT_METHOD_CATEGORY_KO, // Korean input methods |
| INPUT_METHOD_CATEGORY_M17N, // Multilingualization input methods |
| INPUT_METHOD_CATEGORY_T13N, // Transliteration input methods |
| INPUT_METHOD_CATEGORY_MAX |
| }; |
| |
| InputMethodCategory GetInputMethodCategory(const std::string& input_method_id, |
| char* first_char = NULL) { |
| const std::string component_id = |
| extension_ime_util::GetComponentIDByInputMethodID(input_method_id); |
| InputMethodCategory category = INPUT_METHOD_CATEGORY_UNKNOWN; |
| char ch = 0; |
| if (StartsWithASCII(component_id, "xkb:", true)) { |
| ch = component_id[4]; |
| category = INPUT_METHOD_CATEGORY_XKB; |
| } else if (StartsWithASCII(component_id, "zh-", true)) { |
| size_t pos = component_id.find("-t-i0-"); |
| if (pos > 0) |
| pos += 6; |
| ch = component_id[pos]; |
| category = INPUT_METHOD_CATEGORY_ZH; |
| } else if (StartsWithASCII(component_id, "nacl_mozc_", true)) { |
| ch = component_id[10]; |
| category = INPUT_METHOD_CATEGORY_JA; |
| } else if (StartsWithASCII(component_id, "hangul_", true)) { |
| ch = component_id[7]; |
| category = INPUT_METHOD_CATEGORY_KO; |
| } else if (StartsWithASCII(component_id, "vkd_", true)) { |
| ch = component_id[4]; |
| category = INPUT_METHOD_CATEGORY_M17N; |
| } else if (component_id.find("-t-i0-") > 0) { |
| ch = component_id[0]; |
| category = INPUT_METHOD_CATEGORY_T13N; |
| } |
| |
| if (first_char) |
| *first_char = ch; |
| return category; |
| } |
| |
| } // namespace |
| |
| // ------------------------ InputMethodManagerImpl::StateImpl |
| |
| InputMethodManagerImpl::StateImpl::StateImpl(InputMethodManagerImpl* manager, |
| Profile* profile) |
| : profile(profile), manager_(manager) { |
| } |
| |
| InputMethodManagerImpl::StateImpl::~StateImpl() { |
| } |
| |
| void InputMethodManagerImpl::StateImpl::InitFrom(const StateImpl& other) { |
| previous_input_method = other.previous_input_method; |
| current_input_method = other.current_input_method; |
| |
| active_input_method_ids = other.active_input_method_ids; |
| |
| pending_input_method_id = other.pending_input_method_id; |
| |
| enabled_extension_imes = other.enabled_extension_imes; |
| extra_input_methods = other.extra_input_methods; |
| } |
| |
| bool InputMethodManagerImpl::StateImpl::IsActive() const { |
| return manager_->state_.get() == this; |
| } |
| |
| std::string InputMethodManagerImpl::StateImpl::Dump() const { |
| std::ostringstream os; |
| |
| os << "################# " |
| << (profile ? profile->GetProfileName() : std::string("NULL")) |
| << " #################\n"; |
| |
| os << "previous_input_method: '" |
| << previous_input_method.GetPreferredKeyboardLayout() << "'\n"; |
| os << "current_input_method: '" |
| << current_input_method.GetPreferredKeyboardLayout() << "'\n"; |
| os << "active_input_method_ids (size=" << active_input_method_ids.size() |
| << "):"; |
| for (size_t i = 0; i < active_input_method_ids.size(); ++i) { |
| os << " '" << active_input_method_ids[i] << "',"; |
| } |
| os << "\n"; |
| os << "enabled_extension_imes (size=" << enabled_extension_imes.size() |
| << "):"; |
| for (size_t i = 0; i < enabled_extension_imes.size(); ++i) { |
| os << " '" << enabled_extension_imes[i] << "'\n"; |
| } |
| os << "\n"; |
| os << "extra_input_methods (size=" << extra_input_methods.size() << "):"; |
| for (std::map<std::string, InputMethodDescriptor>::const_iterator it = |
| extra_input_methods.begin(); |
| it != extra_input_methods.end(); |
| ++it) { |
| os << " '" << it->first << "' => '" << it->second.id() << "',\n"; |
| } |
| os << "pending_input_method_id: '" << pending_input_method_id << "'\n"; |
| |
| return os.str(); |
| } |
| |
| scoped_refptr<InputMethodManager::State> |
| InputMethodManagerImpl::StateImpl::Clone() const { |
| scoped_refptr<StateImpl> new_state(new StateImpl(this->manager_, profile)); |
| new_state->InitFrom(*this); |
| return scoped_refptr<InputMethodManager::State>(new_state.get()); |
| } |
| |
| scoped_ptr<InputMethodDescriptors> |
| InputMethodManagerImpl::StateImpl::GetActiveInputMethods() const { |
| scoped_ptr<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 = |
| manager_->util_.GetInputMethodDescriptorFromId(input_method_id); |
| if (descriptor) { |
| result->push_back(*descriptor); |
| } else { |
| std::map<std::string, InputMethodDescriptor>::const_iterator ix = |
| extra_input_methods.find(input_method_id); |
| if (ix != extra_input_methods.end()) |
| result->push_back(ix->second); |
| else |
| DVLOG(1) << "Descriptor is not found for: " << input_method_id; |
| } |
| } |
| if (result->empty()) { |
| // Initially |active_input_method_ids| is empty. browser_tests might take |
| // this path. |
| result->push_back( |
| InputMethodUtil::GetFallbackInputMethodDescriptor()); |
| } |
| return result.Pass(); |
| } |
| |
| const std::vector<std::string>& |
| InputMethodManagerImpl::StateImpl::GetActiveInputMethodIds() const { |
| return active_input_method_ids; |
| } |
| |
| size_t InputMethodManagerImpl::StateImpl::GetNumActiveInputMethods() const { |
| return active_input_method_ids.size(); |
| } |
| |
| const InputMethodDescriptor* |
| InputMethodManagerImpl::StateImpl::GetInputMethodFromId( |
| const std::string& input_method_id) const { |
| const InputMethodDescriptor* ime = |
| manager_->util_.GetInputMethodDescriptorFromId(input_method_id); |
| if (!ime) { |
| std::map<std::string, InputMethodDescriptor>::const_iterator ix = |
| extra_input_methods.find(input_method_id); |
| if (ix != extra_input_methods.end()) |
| ime = &ix->second; |
| } |
| return ime; |
| } |
| |
| void InputMethodManagerImpl::StateImpl::EnableLoginLayouts( |
| const std::string& language_code, |
| const std::vector<std::string>& initial_layouts) { |
| if (manager_->ui_session_ == STATE_TERMINATING) |
| return; |
| |
| // First, hardware keyboard layout should be shown. |
| std::vector<std::string> candidates = |
| manager_->util_.GetHardwareLoginInputMethodIds(); |
| |
| // Second, locale based input method should be shown. |
| // Add input methods associated with the language. |
| std::vector<std::string> layouts_from_locale; |
| manager_->util_.GetInputMethodIdsFromLanguageCode( |
| language_code, kKeyboardLayoutsOnly, &layouts_from_locale); |
| candidates.insert(candidates.end(), layouts_from_locale.begin(), |
| layouts_from_locale.end()); |
| |
| std::vector<std::string> layouts; |
| // First, add the initial input method ID, if it's requested, to |
| // layouts, so it appears first on the list of active input |
| // methods at the input language status menu. |
| for (size_t i = 0; i < initial_layouts.size(); ++i) { |
| if (manager_->util_.IsValidInputMethodId(initial_layouts[i])) { |
| if (manager_->IsLoginKeyboard(initial_layouts[i])) { |
| layouts.push_back(initial_layouts[i]); |
| } else { |
| DVLOG(1) |
| << "EnableLoginLayouts: ignoring non-login initial keyboard layout:" |
| << initial_layouts[i]; |
| } |
| } else if (!initial_layouts[i].empty()) { |
| DVLOG(1) << "EnableLoginLayouts: ignoring non-keyboard or invalid ID: " |
| << initial_layouts[i]; |
| } |
| } |
| |
| // Add candidates to layouts, 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 (!Contains(layouts, candidate) && manager_->IsLoginKeyboard(candidate)) |
| layouts.push_back(candidate); |
| } |
| |
| manager_->MigrateInputMethods(&layouts); |
| active_input_method_ids.swap(layouts); |
| |
| if (IsActive()) { |
| // Initialize candidate window controller and widgets such as |
| // candidate window, infolist and mode indicator. Note, mode |
| // indicator is used by only keyboard layout input methods. |
| if (active_input_method_ids.size() > 1) |
| manager_->MaybeInitializeCandidateWindowController(); |
| |
| // you can pass empty |initial_layout|. |
| ChangeInputMethod(initial_layouts.empty() |
| ? std::string() |
| : extension_ime_util::GetInputMethodIDByEngineID( |
| initial_layouts[0]), |
| false); |
| } |
| } |
| |
| void InputMethodManagerImpl::StateImpl::EnableLockScreenLayouts() { |
| std::set<std::string> added_ids; |
| |
| const std::vector<std::string>& hardware_keyboard_ids = |
| manager_->util_.GetHardwareLoginInputMethodIds(); |
| |
| std::vector<std::string> new_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]; |
| // Skip if it's not a keyboard layout. Drop input methods including |
| // extension ones. |
| if (!manager_->IsLoginKeyboard(input_method_id) || |
| added_ids.count(input_method_id)) { |
| continue; |
| } |
| new_active_input_method_ids.push_back(input_method_id); |
| added_ids.insert(input_method_id); |
| } |
| |
| // We'll add the hardware keyboard if it's not included in |
| // |active_input_method_ids| so that the user can always use the hardware |
| // keyboard on the screen locker. |
| for (size_t i = 0; i < hardware_keyboard_ids.size(); ++i) { |
| if (added_ids.count(hardware_keyboard_ids[i])) |
| continue; |
| new_active_input_method_ids.push_back(hardware_keyboard_ids[i]); |
| added_ids.insert(hardware_keyboard_ids[i]); |
| } |
| |
| active_input_method_ids.swap(new_active_input_method_ids); |
| |
| // Re-check current_input_method. |
| ChangeInputMethod(current_input_method.id(), false); |
| } |
| |
| // Adds new input method to given list. |
| bool InputMethodManagerImpl::StateImpl::EnableInputMethodImpl( |
| const std::string& input_method_id, |
| std::vector<std::string>* new_active_input_method_ids) const { |
| DCHECK(new_active_input_method_ids); |
| if (!manager_->util_.IsValidInputMethodId(input_method_id)) { |
| DVLOG(1) << "EnableInputMethod: Invalid ID: " << input_method_id; |
| return false; |
| } |
| |
| if (!Contains(*new_active_input_method_ids, input_method_id)) |
| new_active_input_method_ids->push_back(input_method_id); |
| |
| return true; |
| } |
| |
| bool InputMethodManagerImpl::StateImpl::EnableInputMethod( |
| const std::string& input_method_id) { |
| if (!EnableInputMethodImpl(input_method_id, &active_input_method_ids)) |
| return false; |
| |
| manager_->ReconfigureIMFramework(this); |
| return true; |
| } |
| |
| bool InputMethodManagerImpl::StateImpl::ReplaceEnabledInputMethods( |
| const std::vector<std::string>& new_active_input_method_ids) { |
| if (manager_->ui_session_ == STATE_TERMINATING) |
| return false; |
| |
| // Filter unknown or obsolete IDs. |
| std::vector<std::string> new_active_input_method_ids_filtered; |
| |
| for (size_t i = 0; i < new_active_input_method_ids.size(); ++i) |
| EnableInputMethodImpl(new_active_input_method_ids[i], |
| &new_active_input_method_ids_filtered); |
| |
| if (new_active_input_method_ids_filtered.empty()) { |
| DVLOG(1) << "ReplaceEnabledInputMethods: No valid input method ID"; |
| return false; |
| } |
| |
| // Copy extension IDs to |new_active_input_method_ids_filtered|. We have to |
| // keep relative order of the extension 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]; |
| if (extension_ime_util::IsExtensionIME(input_method_id)) |
| new_active_input_method_ids_filtered.push_back(input_method_id); |
| } |
| active_input_method_ids.swap(new_active_input_method_ids_filtered); |
| manager_->MigrateInputMethods(&active_input_method_ids); |
| |
| manager_->ReconfigureIMFramework(this); |
| |
| // If |current_input_method| is no longer in |active_input_method_ids|, |
| // ChangeInputMethod() picks the first one in |active_input_method_ids|. |
| ChangeInputMethod(current_input_method.id(), false); |
| |
| // Record histogram for active input method count. |
| UMA_HISTOGRAM_COUNTS("InputMethod.ActiveCount", |
| active_input_method_ids.size()); |
| |
| return true; |
| } |
| |
| void InputMethodManagerImpl::StateImpl::ChangeInputMethod( |
| const std::string& input_method_id, |
| bool show_message) { |
| if (manager_->ui_session_ == STATE_TERMINATING) |
| return; |
| |
| bool notify_menu = false; |
| // For 3rd party IME, when the user just logged in, SetEnabledExtensionImes |
| // happens after activating the 3rd party IME. |
| // So here to record the 3rd party IME to be activated, and activate it |
| // when SetEnabledExtensionImes happens later. |
| if (MethodAwaitsExtensionLoad(input_method_id)) |
| pending_input_method_id = input_method_id; |
| |
| // Always lookup input method, even if it is the same as |
| // |current_input_method| because If it is no longer in |
| // |active_input_method_ids|, pick the first one in |
| // |active_input_method_ids|. |
| const InputMethodDescriptor* descriptor = |
| manager_->LookupInputMethod(input_method_id, this); |
| if (descriptor->id() != current_input_method.id()) { |
| previous_input_method = current_input_method; |
| current_input_method = *descriptor; |
| notify_menu = true; |
| } |
| |
| // Always change input method even if it is the same. |
| // TODO(komatsu): Revisit if this is neccessary. |
| if (IsActive()) |
| manager_->ChangeInputMethodInternal(*descriptor, show_message, notify_menu); |
| manager_->RecordInputMethodUsage(current_input_method.id()); |
| } |
| |
| bool InputMethodManagerImpl::StateImpl::MethodAwaitsExtensionLoad( |
| const std::string& input_method_id) const { |
| // For 3rd party IME, when the user just logged in, SetEnabledExtensionImes |
| // happens after activating the 3rd party IME. |
| // So here to record the 3rd party IME to be activated, and activate it |
| // when SetEnabledExtensionImes happens later. |
| return !InputMethodIsActivated(input_method_id) && |
| extension_ime_util::IsExtensionIME(input_method_id); |
| } |
| |
| void InputMethodManagerImpl::StateImpl::AddInputMethodExtension( |
| const std::string& extension_id, |
| const InputMethodDescriptors& descriptors, |
| InputMethodEngineInterface* engine) { |
| if (manager_->ui_session_ == STATE_TERMINATING) |
| return; |
| |
| DCHECK(engine); |
| |
| manager_->engine_map_[extension_id] = engine; |
| |
| bool contain = false; |
| for (size_t i = 0; i < descriptors.size(); i++) { |
| const InputMethodDescriptor& descriptor = descriptors[i]; |
| const std::string& id = descriptor.id(); |
| extra_input_methods[id] = descriptor; |
| if (Contains(enabled_extension_imes, id)) { |
| if (!Contains(active_input_method_ids, id)) { |
| active_input_method_ids.push_back(id); |
| } else { |
| DVLOG(1) << "AddInputMethodExtension: already added: " << id << ", " |
| << descriptor.name(); |
| } |
| contain = true; |
| } |
| } |
| |
| if (IsActive()) { |
| if (extension_id == extension_ime_util::GetExtensionIDFromInputMethodID( |
| current_input_method.id())) { |
| IMEBridge::Get()->SetCurrentEngineHandler(engine); |
| engine->Enable(extension_ime_util::GetComponentIDByInputMethodID( |
| current_input_method.id())); |
| } |
| |
| // Ensure that the input method daemon is running. |
| if (contain) |
| manager_->MaybeInitializeCandidateWindowController(); |
| } |
| } |
| |
| void InputMethodManagerImpl::StateImpl::RemoveInputMethodExtension( |
| const std::string& extension_id) { |
| // Remove the active input methods with |extension_id|. |
| std::vector<std::string> new_active_input_method_ids; |
| for (size_t i = 0; i < active_input_method_ids.size(); ++i) { |
| if (extension_id != extension_ime_util::GetExtensionIDFromInputMethodID( |
| active_input_method_ids[i])) |
| new_active_input_method_ids.push_back(active_input_method_ids[i]); |
| } |
| active_input_method_ids.swap(new_active_input_method_ids); |
| |
| // Remove the extra input methods with |extension_id|. |
| std::map<std::string, InputMethodDescriptor> new_extra_input_methods; |
| for (std::map<std::string, InputMethodDescriptor>::iterator i = |
| extra_input_methods.begin(); |
| i != extra_input_methods.end(); |
| ++i) { |
| if (extension_id != |
| extension_ime_util::GetExtensionIDFromInputMethodID(i->first)) |
| new_extra_input_methods[i->first] = i->second; |
| } |
| extra_input_methods.swap(new_extra_input_methods); |
| |
| if (IsActive()) { |
| if (IMEBridge::Get()->GetCurrentEngineHandler() == |
| manager_->engine_map_[extension_id]) { |
| IMEBridge::Get()->SetCurrentEngineHandler(NULL); |
| } |
| manager_->engine_map_.erase(extension_id); |
| } |
| |
| // If |current_input_method| is no longer in |active_input_method_ids|, |
| // switch to the first one in |active_input_method_ids|. |
| ChangeInputMethod(current_input_method.id(), false); |
| } |
| |
| void InputMethodManagerImpl::StateImpl::GetInputMethodExtensions( |
| InputMethodDescriptors* result) { |
| // Build the extension input method descriptors from the extra input |
| // methods cache |extra_input_methods|. |
| std::map<std::string, InputMethodDescriptor>::iterator iter; |
| for (iter = extra_input_methods.begin(); iter != extra_input_methods.end(); |
| ++iter) { |
| if (extension_ime_util::IsExtensionIME(iter->first)) |
| result->push_back(iter->second); |
| } |
| } |
| |
| void InputMethodManagerImpl::StateImpl::SetEnabledExtensionImes( |
| std::vector<std::string>* ids) { |
| enabled_extension_imes.clear(); |
| enabled_extension_imes.insert( |
| enabled_extension_imes.end(), ids->begin(), ids->end()); |
| bool active_imes_changed = false; |
| bool switch_to_pending = false; |
| |
| for (std::map<std::string, InputMethodDescriptor>::iterator extra_iter = |
| extra_input_methods.begin(); |
| extra_iter != extra_input_methods.end(); |
| ++extra_iter) { |
| if (extension_ime_util::IsComponentExtensionIME(extra_iter->first)) |
| continue; // Do not filter component extension. |
| |
| if (pending_input_method_id == extra_iter->first) |
| switch_to_pending = true; |
| |
| std::vector<std::string>::iterator active_iter = |
| std::find(active_input_method_ids.begin(), |
| active_input_method_ids.end(), |
| extra_iter->first); |
| |
| bool active = active_iter != active_input_method_ids.end(); |
| bool enabled = Contains(enabled_extension_imes, extra_iter->first); |
| |
| if (active && !enabled) |
| active_input_method_ids.erase(active_iter); |
| |
| if (!active && enabled) |
| active_input_method_ids.push_back(extra_iter->first); |
| |
| if (active == !enabled) |
| active_imes_changed = true; |
| } |
| |
| if (IsActive() && active_imes_changed) { |
| manager_->MaybeInitializeCandidateWindowController(); |
| |
| if (switch_to_pending) { |
| ChangeInputMethod(pending_input_method_id, false); |
| pending_input_method_id.clear(); |
| } else { |
| // If |current_input_method| is no longer in |active_input_method_ids_|, |
| // switch to the first one in |active_input_method_ids_|. |
| ChangeInputMethod(current_input_method.id(), false); |
| } |
| } |
| } |
| |
| void InputMethodManagerImpl::StateImpl::SetInputMethodLoginDefaultFromVPD( |
| const std::string& locale, |
| const std::string& oem_layout) { |
| std::string layout; |
| if (!oem_layout.empty()) { |
| // If the OEM layout information is provided, use it. |
| layout = oem_layout; |
| } else { |
| // Otherwise, determine the hardware keyboard from the locale. |
| std::vector<std::string> input_method_ids; |
| if (manager_->util_.GetInputMethodIdsFromLanguageCode( |
| locale, |
| chromeos::input_method::kKeyboardLayoutsOnly, |
| &input_method_ids)) { |
| // The output list |input_method_ids| is sorted by popularity, hence |
| // input_method_ids[0] now contains the most popular keyboard layout |
| // for the given locale. |
| DCHECK_GE(input_method_ids.size(), 1U); |
| layout = input_method_ids[0]; |
| } |
| } |
| |
| if (layout.empty()) |
| return; |
| |
| std::vector<std::string> layouts; |
| base::SplitString(layout, ',', &layouts); |
| manager_->MigrateInputMethods(&layouts); |
| |
| PrefService* prefs = g_browser_process->local_state(); |
| prefs->SetString(prefs::kHardwareKeyboardLayout, JoinString(layouts, ",")); |
| |
| // This asks the file thread to save the prefs (i.e. doesn't block). |
| // The latest values of Local State reside in memory so we can safely |
| // get the value of kHardwareKeyboardLayout even if the data is not |
| // yet saved to disk. |
| prefs->CommitPendingWrite(); |
| |
| manager_->util_.UpdateHardwareLayoutCache(); |
| |
| EnableLoginLayouts(locale, layouts); |
| manager_->LoadNecessaryComponentExtensions(this); |
| } |
| |
| void InputMethodManagerImpl::StateImpl::SetInputMethodLoginDefault() { |
| // Set up keyboards. For example, when |locale| is "en-US", enable US qwerty |
| // and US dvorak keyboard layouts. |
| if (g_browser_process && g_browser_process->local_state()) { |
| const std::string locale = g_browser_process->GetApplicationLocale(); |
| // If the preferred keyboard for the login screen has been saved, use it. |
| PrefService* prefs = g_browser_process->local_state(); |
| std::string initial_input_method_id = |
| prefs->GetString(chromeos::language_prefs::kPreferredKeyboardLayout); |
| std::vector<std::string> input_methods_to_be_enabled; |
| if (initial_input_method_id.empty()) { |
| // If kPreferredKeyboardLayout is not specified, use the hardware layout. |
| input_methods_to_be_enabled = |
| manager_->util_.GetHardwareLoginInputMethodIds(); |
| } else { |
| input_methods_to_be_enabled.push_back(initial_input_method_id); |
| } |
| EnableLoginLayouts(locale, input_methods_to_be_enabled); |
| manager_->LoadNecessaryComponentExtensions(this); |
| } |
| } |
| |
| bool InputMethodManagerImpl::StateImpl::SwitchToNextInputMethod() { |
| // Sanity checks. |
| if (active_input_method_ids.empty()) { |
| DVLOG(1) << "active input method is empty"; |
| return false; |
| } |
| |
| if (current_input_method.id().empty()) { |
| DVLOG(1) << "current_input_method is unknown"; |
| return false; |
| } |
| |
| // Do not consume key event if there is only one input method is enabled. |
| // Ctrl+Space or Alt+Shift may be used by other application. |
| if (active_input_method_ids.size() == 1) |
| return false; |
| |
| // Find the next input method and switch to it. |
| SwitchToNextInputMethodInternal(active_input_method_ids, |
| current_input_method.id()); |
| return true; |
| } |
| |
| bool InputMethodManagerImpl::StateImpl::SwitchToPreviousInputMethod( |
| const ui::Accelerator& accelerator) { |
| // Sanity check. |
| if (active_input_method_ids.empty()) { |
| DVLOG(1) << "active input method is empty"; |
| return false; |
| } |
| |
| // Do not consume key event if there is only one input method is enabled. |
| // Ctrl+Space or Alt+Shift may be used by other application. |
| if (active_input_method_ids.size() == 1) |
| return false; |
| |
| if (accelerator.type() == ui::ET_KEY_RELEASED) |
| return true; |
| |
| 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, true); |
| |
| return true; |
| } |
| |
| bool InputMethodManagerImpl::StateImpl::SwitchInputMethod( |
| const ui::Accelerator& accelerator) { |
| // Sanity check. |
| if (active_input_method_ids.empty()) { |
| DVLOG(1) << "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( |
| extension_ime_util::GetInputMethodIDByEngineID("nacl_mozc_jp")); |
| break; |
| case ui::VKEY_NONCONVERT: // Muhenkan key on JP106 keyboard |
| input_method_ids_to_switch.push_back( |
| extension_ime_util::GetInputMethodIDByEngineID("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( |
| extension_ime_util::GetInputMethodIDByEngineID("nacl_mozc_jp")); |
| input_method_ids_to_switch.push_back( |
| extension_ime_util::GetInputMethodIDByEngineID("xkb:jp::jpn")); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| if (input_method_ids_to_switch.empty()) { |
| DVLOG(1) << "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 (Contains(active_input_method_ids, id)) |
| 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; |
| } |
| |
| SwitchToNextInputMethodInternal(ids, current_input_method.id()); |
| return true; // consume the accelerator. |
| } |
| |
| void InputMethodManagerImpl::StateImpl::SwitchToNextInputMethodInternal( |
| const std::vector<std::string>& input_method_ids, |
| const std::string& current_input_methodid) { |
| std::vector<std::string>::const_iterator iter = std::find( |
| input_method_ids.begin(), input_method_ids.end(), current_input_methodid); |
| if (iter != input_method_ids.end()) |
| ++iter; |
| if (iter == input_method_ids.end()) |
| iter = input_method_ids.begin(); |
| ChangeInputMethod(*iter, true); |
| } |
| |
| InputMethodDescriptor InputMethodManagerImpl::StateImpl::GetCurrentInputMethod() |
| const { |
| if (current_input_method.id().empty()) |
| return InputMethodUtil::GetFallbackInputMethodDescriptor(); |
| |
| return current_input_method; |
| } |
| |
| bool InputMethodManagerImpl::StateImpl::InputMethodIsActivated( |
| const std::string& input_method_id) const { |
| return Contains(active_input_method_ids, input_method_id); |
| } |
| |
| // ------------------------ InputMethodManagerImpl |
| bool InputMethodManagerImpl::IsLoginKeyboard( |
| const std::string& layout) const { |
| return util_.IsLoginKeyboard(layout); |
| } |
| |
| bool InputMethodManagerImpl::MigrateInputMethods( |
| std::vector<std::string>* input_method_ids) { |
| return util_.MigrateInputMethods(input_method_ids); |
| } |
| |
| // Starts or stops the system input method framework as needed. |
| void InputMethodManagerImpl::ReconfigureIMFramework( |
| InputMethodManagerImpl::StateImpl* state) { |
| LoadNecessaryComponentExtensions(state); |
| |
| // Initialize candidate window controller and widgets such as |
| // candidate window, infolist and mode indicator. Note, mode |
| // indicator is used by only keyboard layout input methods. |
| if (state_.get() == state) |
| MaybeInitializeCandidateWindowController(); |
| } |
| |
| void InputMethodManagerImpl::SetState( |
| scoped_refptr<InputMethodManager::State> state) { |
| DCHECK(state.get()); |
| InputMethodManagerImpl::StateImpl* new_impl_state = |
| static_cast<InputMethodManagerImpl::StateImpl*>(state.get()); |
| #if defined(USE_ATHENA) |
| const bool profile_changed = (state_.get() |
| ? state_->profile != new_impl_state->profile |
| : true); |
| #endif |
| |
| state_ = new_impl_state; |
| |
| if (state_.get() && state_->active_input_method_ids.size()) { |
| #if defined(USE_ATHENA) |
| if (profile_changed) |
| LoadNecessaryComponentExtensions(state_.get()); |
| #endif |
| // Initialize candidate window controller and widgets such as |
| // candidate window, infolist and mode indicator. Note, mode |
| // indicator is used by only keyboard layout input methods. |
| MaybeInitializeCandidateWindowController(); |
| |
| // Always call ChangeInputMethodInternal even when the input method id |
| // remain unchanged, because onActivate event needs to be sent to IME |
| // extension to update the current screen type correctly. |
| ChangeInputMethodInternal(state_->current_input_method, |
| false /* show_message */, |
| true /* notify_menu */); |
| } |
| } |
| |
| scoped_refptr<InputMethodManager::State> |
| InputMethodManagerImpl::GetActiveIMEState() { |
| return scoped_refptr<InputMethodManager::State>(state_.get()); |
| } |
| |
| InputMethodManagerImpl::InputMethodManagerImpl( |
| scoped_ptr<InputMethodDelegate> delegate, |
| bool enable_extension_loading) |
| : delegate_(delegate.Pass()), |
| ui_session_(STATE_LOGIN_SCREEN), |
| state_(NULL), |
| util_(delegate_.get()), |
| component_extension_ime_manager_(new ComponentExtensionIMEManager()), |
| enable_extension_loading_(enable_extension_loading) { |
| if (base::SysInfo::IsRunningOnChromeOS()) |
| keyboard_.reset(ImeKeyboard::Create()); |
| else |
| keyboard_.reset(new FakeImeKeyboard()); |
| |
| // Initializes the system IME list. |
| scoped_ptr<ComponentExtensionIMEManagerDelegate> comp_delegate( |
| new ComponentExtensionIMEManagerImpl()); |
| component_extension_ime_manager_->Initialize(comp_delegate.Pass()); |
| const InputMethodDescriptors& descriptors = |
| component_extension_ime_manager_->GetAllIMEAsInputMethodDescriptor(); |
| util_.ResetInputMethods(descriptors); |
| |
| // Initializes the stat id map. |
| std::map<int, std::vector<std::string> > buckets; |
| for (InputMethodDescriptors::const_iterator it = descriptors.begin(); |
| it != descriptors.end(); ++it) { |
| char first_char; |
| int cat_id = static_cast<int>( |
| GetInputMethodCategory(it->id(), &first_char)); |
| int key = cat_id * 1000 + first_char; |
| buckets[key].push_back(it->id()); |
| } |
| for (std::map<int, std::vector<std::string>>::iterator i = |
| buckets.begin(); i != buckets.end(); ++i) { |
| std::sort(i->second.begin(), i->second.end()); |
| for (size_t j = 0; j < i->second.size() && j < 100; ++j) { |
| int key = i->first * 100 + j; |
| stat_id_map_[i->second[j]] = key; |
| } |
| } |
| } |
| |
| InputMethodManagerImpl::~InputMethodManagerImpl() { |
| if (candidate_window_controller_.get()) |
| candidate_window_controller_->RemoveObserver(this); |
| } |
| |
| void InputMethodManagerImpl::RecordInputMethodUsage( |
| std::string input_method_id) { |
| UMA_HISTOGRAM_ENUMERATION("InputMethod.Category", |
| GetInputMethodCategory(input_method_id), |
| INPUT_METHOD_CATEGORY_MAX); |
| UMA_HISTOGRAM_SPARSE_SLOWLY("InputMethod.ID", |
| stat_id_map_[input_method_id]); |
| } |
| |
| void InputMethodManagerImpl::AddObserver( |
| InputMethodManager::Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void InputMethodManagerImpl::AddCandidateWindowObserver( |
| InputMethodManager::CandidateWindowObserver* observer) { |
| candidate_window_observers_.AddObserver(observer); |
| } |
| |
| void InputMethodManagerImpl::RemoveObserver( |
| InputMethodManager::Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void InputMethodManagerImpl::RemoveCandidateWindowObserver( |
| InputMethodManager::CandidateWindowObserver* observer) { |
| candidate_window_observers_.RemoveObserver(observer); |
| } |
| |
| InputMethodManager::UISessionState InputMethodManagerImpl::GetUISessionState() { |
| return ui_session_; |
| } |
| |
| void InputMethodManagerImpl::SetUISessionState(UISessionState new_ui_session) { |
| ui_session_ = new_ui_session; |
| switch (ui_session_) { |
| case STATE_LOGIN_SCREEN: |
| break; |
| case STATE_BROWSER_SCREEN: |
| break; |
| case STATE_LOCK_SCREEN: |
| break; |
| case STATE_TERMINATING: { |
| if (candidate_window_controller_.get()) |
| candidate_window_controller_.reset(); |
| break; |
| } |
| } |
| } |
| |
| scoped_ptr<InputMethodDescriptors> |
| InputMethodManagerImpl::GetSupportedInputMethods() const { |
| return scoped_ptr<InputMethodDescriptors>(new InputMethodDescriptors).Pass(); |
| } |
| |
| const InputMethodDescriptor* InputMethodManagerImpl::LookupInputMethod( |
| const std::string& input_method_id, |
| InputMethodManagerImpl::StateImpl* state) { |
| DCHECK(state); |
| |
| std::string input_method_id_to_switch = input_method_id; |
| |
| // Sanity check |
| if (!state->InputMethodIsActivated(input_method_id)) { |
| scoped_ptr<InputMethodDescriptors> input_methods( |
| state->GetActiveInputMethods()); |
| DCHECK(!input_methods->empty()); |
| input_method_id_to_switch = input_methods->at(0).id(); |
| if (!input_method_id.empty()) { |
| DVLOG(1) << "Can't change the current input method to " |
| << input_method_id << " since the engine is not enabled. " |
| << "Switch to " << input_method_id_to_switch << " instead."; |
| } |
| } |
| |
| const InputMethodDescriptor* descriptor = NULL; |
| if (extension_ime_util::IsExtensionIME(input_method_id_to_switch)) { |
| DCHECK(state->extra_input_methods.find(input_method_id_to_switch) != |
| state->extra_input_methods.end()); |
| descriptor = &(state->extra_input_methods[input_method_id_to_switch]); |
| } else { |
| descriptor = |
| util_.GetInputMethodDescriptorFromId(input_method_id_to_switch); |
| if (!descriptor) |
| LOG(ERROR) << "Unknown input method id: " << input_method_id_to_switch; |
| } |
| DCHECK(descriptor); |
| return descriptor; |
| } |
| |
| void InputMethodManagerImpl::ChangeInputMethodInternal( |
| const InputMethodDescriptor& descriptor, |
| bool show_message, |
| bool notify_menu) { |
| // No need to switch input method when terminating. |
| if (ui_session_ == STATE_TERMINATING) |
| return; |
| |
| if (candidate_window_controller_.get()) |
| candidate_window_controller_->Hide(); |
| |
| if (notify_menu) { |
| // Clear property list. Property list would be updated by |
| // extension IMEs via InputMethodEngine::(Set|Update)MenuItems. |
| // If the current input method is a keyboard layout, empty |
| // properties are sufficient. |
| const ash::ime::InputMethodMenuItemList empty_menu_item_list; |
| ash::ime::InputMethodMenuManager* input_method_menu_manager = |
| ash::ime::InputMethodMenuManager::GetInstance(); |
| input_method_menu_manager->SetCurrentInputMethodMenuItemList( |
| empty_menu_item_list); |
| } |
| |
| // Disable the current engine handler. |
| IMEEngineHandlerInterface* engine = |
| IMEBridge::Get()->GetCurrentEngineHandler(); |
| if (engine) |
| engine->Disable(); |
| |
| // Configure the next engine handler. |
| // This must be after |current_input_method| has been set to new input |
| // method, because engine's Enable() method needs to access it. |
| const std::string& extension_id = |
| extension_ime_util::GetExtensionIDFromInputMethodID(descriptor.id()); |
| const std::string& component_id = |
| extension_ime_util::GetComponentIDByInputMethodID(descriptor.id()); |
| engine = engine_map_[extension_id]; |
| |
| IMEBridge::Get()->SetCurrentEngineHandler(engine); |
| |
| if (engine) { |
| engine->Enable(component_id); |
| } else { |
| // If no engine to enable, cancel the virtual keyboard url override so that |
| // it can use the fallback system virtual keyboard UI. |
| keyboard::SetOverrideContentUrl(GURL()); |
| keyboard::KeyboardController* keyboard_controller = |
| keyboard::KeyboardController::GetInstance(); |
| if (keyboard_controller) |
| keyboard_controller->Reload(); |
| } |
| |
| // Change the keyboard layout to a preferred layout for the input method. |
| if (!keyboard_->SetCurrentKeyboardLayoutByName( |
| descriptor.GetPreferredKeyboardLayout())) { |
| LOG(ERROR) << "Failed to change keyboard layout to " |
| << descriptor.GetPreferredKeyboardLayout(); |
| } |
| |
| // Update input method indicators (e.g. "US", "DV") in Chrome windows. |
| FOR_EACH_OBSERVER(InputMethodManager::Observer, |
| observers_, |
| InputMethodChanged(this, show_message)); |
| } |
| |
| void InputMethodManagerImpl::LoadNecessaryComponentExtensions( |
| InputMethodManagerImpl::StateImpl* state) { |
| // Load component extensions but also update |active_input_method_ids| as |
| // some component extension IMEs may have been removed from the Chrome OS |
| // image. If specified component extension IME no longer exists, falling back |
| // to an existing IME. |
| DCHECK(state); |
| std::vector<std::string> unfiltered_input_method_ids; |
| unfiltered_input_method_ids.swap(state->active_input_method_ids); |
| for (size_t i = 0; i < unfiltered_input_method_ids.size(); ++i) { |
| if (!extension_ime_util::IsComponentExtensionIME( |
| unfiltered_input_method_ids[i])) { |
| // Legacy IMEs or xkb layouts are alwayes active. |
| state->active_input_method_ids.push_back(unfiltered_input_method_ids[i]); |
| } else if (component_extension_ime_manager_->IsWhitelisted( |
| unfiltered_input_method_ids[i])) { |
| if (enable_extension_loading_) { |
| component_extension_ime_manager_->LoadComponentExtensionIME( |
| state->profile, unfiltered_input_method_ids[i]); |
| } |
| |
| state->active_input_method_ids.push_back(unfiltered_input_method_ids[i]); |
| } |
| } |
| } |
| |
| void InputMethodManagerImpl::ActivateInputMethodMenuItem( |
| const std::string& key) { |
| DCHECK(!key.empty()); |
| |
| if (ash::ime::InputMethodMenuManager::GetInstance()-> |
| HasInputMethodMenuItemForKey(key)) { |
| IMEEngineHandlerInterface* engine = |
| IMEBridge::Get()->GetCurrentEngineHandler(); |
| if (engine) |
| engine->PropertyActivate(key); |
| return; |
| } |
| |
| DVLOG(1) << "ActivateInputMethodMenuItem: unknown key: " << key; |
| } |
| |
| bool InputMethodManagerImpl::IsISOLevel5ShiftUsedByCurrentInputMethod() const { |
| return keyboard_->IsISOLevel5ShiftAvailable(); |
| } |
| |
| bool InputMethodManagerImpl::IsAltGrUsedByCurrentInputMethod() const { |
| return keyboard_->IsAltGrAvailable(); |
| } |
| |
| ImeKeyboard* InputMethodManagerImpl::GetImeKeyboard() { |
| return keyboard_.get(); |
| } |
| |
| InputMethodUtil* InputMethodManagerImpl::GetInputMethodUtil() { |
| return &util_; |
| } |
| |
| ComponentExtensionIMEManager* |
| InputMethodManagerImpl::GetComponentExtensionIMEManager() { |
| return component_extension_ime_manager_.get(); |
| } |
| |
| scoped_refptr<InputMethodManager::State> InputMethodManagerImpl::CreateNewState( |
| Profile* profile) { |
| StateImpl* new_state = new StateImpl(this, profile); |
| #if defined(USE_ATHENA) |
| // Athena for now doesn't have user preferences for input methods, |
| // therefore no one sets the active input methods in IMF. So just set |
| // the default IME here. |
| // TODO(shuchen): we need to better initialize with user preferences. |
| const InputMethodDescriptor* descriptor = |
| GetInputMethodUtil()->GetInputMethodDescriptorFromId( |
| GetInputMethodUtil()->GetFallbackInputMethodDescriptor().id()); |
| if (descriptor) { |
| new_state->active_input_method_ids.push_back(descriptor->id()); |
| new_state->current_input_method = *descriptor; |
| } |
| #endif |
| return scoped_refptr<InputMethodManager::State>(new_state); |
| } |
| |
| void InputMethodManagerImpl::SetCandidateWindowControllerForTesting( |
| CandidateWindowController* candidate_window_controller) { |
| candidate_window_controller_.reset(candidate_window_controller); |
| candidate_window_controller_->AddObserver(this); |
| } |
| |
| void InputMethodManagerImpl::SetImeKeyboardForTesting(ImeKeyboard* keyboard) { |
| keyboard_.reset(keyboard); |
| } |
| |
| void InputMethodManagerImpl::InitializeComponentExtensionForTesting( |
| scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate) { |
| component_extension_ime_manager_->Initialize(delegate.Pass()); |
| util_.ResetInputMethods( |
| component_extension_ime_manager_->GetAllIMEAsInputMethodDescriptor()); |
| } |
| |
| void InputMethodManagerImpl::CandidateClicked(int index) { |
| IMEEngineHandlerInterface* engine = |
| IMEBridge::Get()->GetCurrentEngineHandler(); |
| if (engine) |
| engine->CandidateClicked(index); |
| } |
| |
| void InputMethodManagerImpl::CandidateWindowOpened() { |
| FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver, |
| candidate_window_observers_, |
| CandidateWindowOpened(this)); |
| } |
| |
| void InputMethodManagerImpl::CandidateWindowClosed() { |
| FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver, |
| candidate_window_observers_, |
| CandidateWindowClosed(this)); |
| } |
| |
| void InputMethodManagerImpl::MaybeInitializeCandidateWindowController() { |
| if (candidate_window_controller_.get()) |
| return; |
| |
| candidate_window_controller_.reset( |
| CandidateWindowController::CreateCandidateWindowController()); |
| candidate_window_controller_->AddObserver(this); |
| } |
| |
| } // namespace input_method |
| } // namespace chromeos |