input_method_manager_impl.cc revision 5e3f23d412006dc4db4e659864679f29341e113f
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/chromeos/input_method/input_method_manager_impl.h"
6
7#include <algorithm>  // std::find
8
9#include "base/basictypes.h"
10#include "base/bind.h"
11#include "base/location.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/strings/string_util.h"
14#include "base/strings/stringprintf.h"
15#include "chrome/browser/chromeos/input_method/candidate_window_controller.h"
16#include "chrome/browser/chromeos/input_method/component_extension_ime_manager_impl.h"
17#include "chrome/browser/chromeos/input_method/input_method_engine_ibus.h"
18#include "chrome/browser/chromeos/language_preferences.h"
19#include "chromeos/dbus/dbus_thread_manager.h"
20#include "chromeos/dbus/ibus/ibus_client.h"
21#include "chromeos/dbus/ibus/ibus_input_context_client.h"
22#include "chromeos/ime/component_extension_ime_manager.h"
23#include "chromeos/ime/extension_ime_util.h"
24#include "chromeos/ime/input_method_delegate.h"
25#include "chromeos/ime/xkeyboard.h"
26#include "third_party/icu/public/common/unicode/uloc.h"
27#include "ui/base/accelerators/accelerator.h"
28
29namespace chromeos {
30namespace input_method {
31
32namespace {
33
34const char nacl_mozc_us_id[] =
35    "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_us";
36const char nacl_mozc_jp_id[] =
37    "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_jp";
38
39bool Contains(const std::vector<std::string>& container,
40              const std::string& value) {
41  return std::find(container.begin(), container.end(), value) !=
42      container.end();
43}
44
45const struct MigrationInputMethodList {
46  const char* old_input_method;
47  const char* new_input_method;
48} kMigrationInputMethodList[] = {
49  { "mozc", "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_us" },
50  { "mozc-jp", "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_jp" },
51  { "mozc-dv", "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_us" },
52};
53
54}  // namespace
55
56InputMethodManagerImpl::InputMethodManagerImpl(
57    scoped_ptr<InputMethodDelegate> delegate)
58    : delegate_(delegate.Pass()),
59      state_(STATE_LOGIN_SCREEN),
60      util_(delegate_.get(), GetSupportedInputMethods()),
61      component_extension_ime_manager_(new ComponentExtensionIMEManager()),
62      weak_ptr_factory_(this) {
63  IBusDaemonController::GetInstance()->AddObserver(this);
64}
65
66InputMethodManagerImpl::~InputMethodManagerImpl() {
67  if (ibus_controller_.get())
68    ibus_controller_->RemoveObserver(this);
69  IBusDaemonController::GetInstance()->RemoveObserver(this);
70  if (candidate_window_controller_.get()) {
71    candidate_window_controller_->RemoveObserver(this);
72    candidate_window_controller_->Shutdown();
73  }
74}
75
76void InputMethodManagerImpl::AddObserver(
77    InputMethodManager::Observer* observer) {
78  observers_.AddObserver(observer);
79}
80
81void InputMethodManagerImpl::AddCandidateWindowObserver(
82    InputMethodManager::CandidateWindowObserver* observer) {
83  candidate_window_observers_.AddObserver(observer);
84}
85
86void InputMethodManagerImpl::RemoveObserver(
87    InputMethodManager::Observer* observer) {
88  observers_.RemoveObserver(observer);
89}
90
91void InputMethodManagerImpl::RemoveCandidateWindowObserver(
92    InputMethodManager::CandidateWindowObserver* observer) {
93  candidate_window_observers_.RemoveObserver(observer);
94}
95
96void InputMethodManagerImpl::SetState(State new_state) {
97  const State old_state = state_;
98  state_ = new_state;
99  switch (state_) {
100    case STATE_LOGIN_SCREEN:
101      break;
102    case STATE_BROWSER_SCREEN:
103      if (old_state == STATE_LOCK_SCREEN)
104        OnScreenUnlocked();
105      break;
106    case STATE_LOCK_SCREEN:
107      OnScreenLocked();
108      break;
109    case STATE_TERMINATING: {
110      if (candidate_window_controller_.get()) {
111        candidate_window_controller_->Shutdown();
112        candidate_window_controller_.reset();
113      }
114      break;
115    }
116  }
117}
118
119scoped_ptr<InputMethodDescriptors>
120InputMethodManagerImpl::GetSupportedInputMethods() const {
121  return whitelist_.GetSupportedInputMethods();
122}
123
124scoped_ptr<InputMethodDescriptors>
125InputMethodManagerImpl::GetActiveInputMethods() const {
126  scoped_ptr<InputMethodDescriptors> result(new InputMethodDescriptors);
127  // Build the active input method descriptors from the active input
128  // methods cache |active_input_method_ids_|.
129  for (size_t i = 0; i < active_input_method_ids_.size(); ++i) {
130    const std::string& input_method_id = active_input_method_ids_[i];
131    const InputMethodDescriptor* descriptor =
132        util_.GetInputMethodDescriptorFromId(input_method_id);
133    if (descriptor) {
134      result->push_back(*descriptor);
135    } else {
136      std::map<std::string, InputMethodDescriptor>::const_iterator ix =
137          extra_input_methods_.find(input_method_id);
138      if (ix != extra_input_methods_.end())
139        result->push_back(ix->second);
140      else
141        DVLOG(1) << "Descriptor is not found for: " << input_method_id;
142    }
143  }
144  if (result->empty()) {
145    // Initially |active_input_method_ids_| is empty. browser_tests might take
146    // this path.
147    result->push_back(
148        InputMethodUtil::GetFallbackInputMethodDescriptor());
149  }
150  return result.Pass();
151}
152
153size_t InputMethodManagerImpl::GetNumActiveInputMethods() const {
154  return active_input_method_ids_.size();
155}
156
157void InputMethodManagerImpl::EnableLayouts(const std::string& language_code,
158                                           const std::string& initial_layout) {
159  if (state_ == STATE_TERMINATING)
160    return;
161
162  std::vector<std::string> candidates;
163  // Add input methods associated with the language.
164  util_.GetInputMethodIdsFromLanguageCode(language_code,
165                                          kKeyboardLayoutsOnly,
166                                          &candidates);
167  // Add the hardware keyboard as well. We should always add this so users
168  // can use the hardware keyboard on the login screen and the screen locker.
169  candidates.push_back(util_.GetHardwareInputMethodId());
170
171  std::vector<std::string> layouts;
172  // First, add the initial input method ID, if it's requested, to
173  // layouts, so it appears first on the list of active input
174  // methods at the input language status menu.
175  if (util_.IsValidInputMethodId(initial_layout) &&
176      InputMethodUtil::IsKeyboardLayout(initial_layout)) {
177    layouts.push_back(initial_layout);
178  } else if (!initial_layout.empty()) {
179    DVLOG(1) << "EnableLayouts: ignoring non-keyboard or invalid ID: "
180             << initial_layout;
181  }
182
183  // Add candidates to layouts, while skipping duplicates.
184  for (size_t i = 0; i < candidates.size(); ++i) {
185    const std::string& candidate = candidates[i];
186    // Not efficient, but should be fine, as the two vectors are very
187    // short (2-5 items).
188    if (!Contains(layouts, candidate))
189      layouts.push_back(candidate);
190  }
191
192  active_input_method_ids_.swap(layouts);
193  ChangeInputMethod(initial_layout);  // you can pass empty |initial_layout|.
194}
195
196bool InputMethodManagerImpl::EnableInputMethods(
197    const std::vector<std::string>& new_active_input_method_ids) {
198  if (state_ == STATE_TERMINATING)
199    return false;
200
201  // Filter unknown or obsolete IDs.
202  std::vector<std::string> new_active_input_method_ids_filtered;
203
204  for (size_t i = 0; i < new_active_input_method_ids.size(); ++i) {
205    const std::string& input_method_id = new_active_input_method_ids[i];
206    if (util_.IsValidInputMethodId(input_method_id))
207      new_active_input_method_ids_filtered.push_back(input_method_id);
208    else
209      DVLOG(1) << "EnableInputMethods: Invalid ID: " << input_method_id;
210  }
211
212  if (new_active_input_method_ids_filtered.empty()) {
213    DVLOG(1) << "EnableInputMethods: No valid input method ID";
214    return false;
215  }
216
217  // Copy extension IDs to |new_active_input_method_ids_filtered|. We have to
218  // keep relative order of the extension input method IDs.
219  for (size_t i = 0; i < active_input_method_ids_.size(); ++i) {
220    const std::string& input_method_id = active_input_method_ids_[i];
221    if (extension_ime_util::IsExtensionIME(input_method_id))
222      new_active_input_method_ids_filtered.push_back(input_method_id);
223  }
224  active_input_method_ids_.swap(new_active_input_method_ids_filtered);
225
226  if (component_extension_ime_manager_->IsInitialized())
227    LoadNecessaryComponentExtensions();
228
229  if (ContainOnlyKeyboardLayout(active_input_method_ids_)) {
230    // Do NOT call ibus_controller_->Stop(); here to work around a crash issue
231    // at crosbug.com/27051.
232    // TODO(yusukes): We can safely call Stop(); here once crosbug.com/26443
233    // is implemented.
234  } else {
235    MaybeInitializeCandidateWindowController();
236    IBusDaemonController::GetInstance()->Start();
237  }
238
239  // If |current_input_method| is no longer in |active_input_method_ids_|,
240  // ChangeInputMethod() picks the first one in |active_input_method_ids_|.
241  ChangeInputMethod(current_input_method_.id());
242  return true;
243}
244
245bool InputMethodManagerImpl::MigrateOldInputMethods(
246    std::vector<std::string>* input_method_ids) {
247  bool rewritten = false;
248  for (size_t i = 0; i < input_method_ids->size(); ++i) {
249    for (size_t j = 0; j < ARRAYSIZE_UNSAFE(kMigrationInputMethodList); ++j) {
250      if (input_method_ids->at(i) ==
251          kMigrationInputMethodList[j].old_input_method) {
252        input_method_ids->at(i).assign(
253            kMigrationInputMethodList[j].new_input_method);
254        rewritten = true;
255      }
256    }
257  }
258  std::vector<std::string>::iterator it =
259      std::unique(input_method_ids->begin(), input_method_ids->end());
260  input_method_ids->resize(std::distance(input_method_ids->begin(), it));
261  return rewritten;
262}
263
264bool InputMethodManagerImpl::SetInputMethodConfig(
265    const std::string& section,
266    const std::string& config_name,
267    const InputMethodConfigValue& value) {
268  DCHECK(section != language_prefs::kGeneralSectionName ||
269         config_name != language_prefs::kPreloadEnginesConfigName);
270
271  if (state_ == STATE_TERMINATING)
272    return false;
273  return ibus_controller_->SetInputMethodConfig(section, config_name, value);
274}
275
276void InputMethodManagerImpl::ChangeInputMethod(
277    const std::string& input_method_id) {
278  ChangeInputMethodInternal(input_method_id, false);
279}
280
281bool InputMethodManagerImpl::ChangeInputMethodInternal(
282    const std::string& input_method_id,
283    bool show_message) {
284  if (state_ == STATE_TERMINATING)
285    return false;
286
287  std::string input_method_id_to_switch = input_method_id;
288
289  // Sanity check.
290  if (!InputMethodIsActivated(input_method_id)) {
291    scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods());
292    DCHECK(!input_methods->empty());
293    input_method_id_to_switch = input_methods->at(0).id();
294    if (!input_method_id.empty()) {
295      DVLOG(1) << "Can't change the current input method to "
296               << input_method_id << " since the engine is not enabled. "
297               << "Switch to " << input_method_id_to_switch << " instead.";
298    }
299  }
300
301  if (!component_extension_ime_manager_->IsInitialized() ||
302      (!InputMethodUtil::IsKeyboardLayout(input_method_id_to_switch) &&
303       !IsIBusConnectionAlive())) {
304    // We can't change input method before the initialization of component
305    // extension ime manager or before connection to ibus-daemon is not
306    // established. ChangeInputMethod will be called with
307    // |pending_input_method_| when the both initialization is done.
308    pending_input_method_ = input_method_id_to_switch;
309    return false;
310  }
311
312  pending_input_method_.clear();
313  IBusInputContextClient* input_context =
314      chromeos::DBusThreadManager::Get()->GetIBusInputContextClient();
315  const std::string current_input_method_id = current_input_method_.id();
316  IBusClient* client = DBusThreadManager::Get()->GetIBusClient();
317  if (InputMethodUtil::IsKeyboardLayout(input_method_id_to_switch)) {
318    FOR_EACH_OBSERVER(InputMethodManager::Observer,
319                      observers_,
320                      InputMethodPropertyChanged(this));
321    // Hack for fixing http://crosbug.com/p/12798
322    // We should notify IME switching to ibus-daemon, otherwise
323    // IBusPreeditFocusMode does not work. To achieve it, change engine to
324    // itself if the next engine is XKB layout.
325    if (current_input_method_id.empty() ||
326        InputMethodUtil::IsKeyboardLayout(current_input_method_id)) {
327      if (input_context)
328        input_context->Reset();
329    } else {
330      if (client)
331        client->SetGlobalEngine(current_input_method_id,
332                                base::Bind(&base::DoNothing));
333    }
334    if (input_context)
335      input_context->SetIsXKBLayout(true);
336  } else {
337    DCHECK(client);
338    client->SetGlobalEngine(input_method_id_to_switch,
339                            base::Bind(&base::DoNothing));
340    if (input_context)
341      input_context->SetIsXKBLayout(false);
342  }
343
344  if (current_input_method_id != input_method_id_to_switch) {
345    // Clear input method properties unconditionally if
346    // |input_method_id_to_switch| is not equal to |current_input_method_id|.
347    //
348    // When switching to another input method and no text area is focused,
349    // RegisterProperties signal for the new input method will NOT be sent
350    // until a text area is focused. Therefore, we have to clear the old input
351    // method properties here to keep the input method switcher status
352    // consistent.
353    //
354    // When |input_method_id_to_switch| and |current_input_method_id| are the
355    // same, the properties shouldn't be cleared. If we do that, something
356    // wrong happens in step #4 below:
357    // 1. Enable "xkb:us::eng" and "mozc". Switch to "mozc".
358    // 2. Focus Omnibox. IME properties for mozc are sent to Chrome.
359    // 3. Switch to "xkb:us::eng". No function in this file is called.
360    // 4. Switch back to "mozc". ChangeInputMethod("mozc") is called, but it's
361    //    basically NOP since ibus-daemon's current IME is already "mozc".
362    //    IME properties are not sent to Chrome for the same reason.
363    // TODO(nona): Revisit above comment once ibus-daemon is gone.
364    ibus_controller_->ClearProperties();
365
366    const InputMethodDescriptor* descriptor = NULL;
367    if (!extension_ime_util::IsExtensionIME(input_method_id_to_switch)) {
368      descriptor =
369          util_.GetInputMethodDescriptorFromId(input_method_id_to_switch);
370    } else {
371      std::map<std::string, InputMethodDescriptor>::const_iterator i =
372          extra_input_methods_.find(input_method_id_to_switch);
373      DCHECK(i != extra_input_methods_.end());
374      descriptor = &(i->second);
375    }
376    DCHECK(descriptor);
377
378    previous_input_method_ = current_input_method_;
379    current_input_method_ = *descriptor;
380  }
381
382  // Change the keyboard layout to a preferred layout for the input method.
383  if (!xkeyboard_->SetCurrentKeyboardLayoutByName(
384          current_input_method_.GetPreferredKeyboardLayout())) {
385    LOG(ERROR) << "Failed to change keyboard layout to "
386               << current_input_method_.GetPreferredKeyboardLayout();
387  }
388
389  // Update input method indicators (e.g. "US", "DV") in Chrome windows.
390  FOR_EACH_OBSERVER(InputMethodManager::Observer,
391                    observers_,
392                    InputMethodChanged(this, show_message));
393  return true;
394}
395
396void InputMethodManagerImpl::OnComponentExtensionInitialized(
397    scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate) {
398  DCHECK(thread_checker_.CalledOnValidThread());
399  component_extension_ime_manager_->Initialize(delegate.Pass());
400  util_.SetComponentExtensions(
401      component_extension_ime_manager_->GetAllIMEAsInputMethodDescriptor());
402
403  LoadNecessaryComponentExtensions();
404
405  if (!pending_input_method_.empty())
406    ChangeInputMethodInternal(pending_input_method_, false);
407
408}
409
410void InputMethodManagerImpl::LoadNecessaryComponentExtensions() {
411  if (!component_extension_ime_manager_->IsInitialized())
412    return;
413  // Load component extensions but also update |active_input_method_ids_| as
414  // some component extension IMEs may have been removed from the Chrome OS
415  // image. If specified component extension IME no longer exists, falling back
416  // to an existing IME.
417  std::vector<std::string> unfiltered_input_method_ids =
418      active_input_method_ids_;
419  active_input_method_ids_.clear();
420  for (size_t i = 0; i < unfiltered_input_method_ids.size(); ++i) {
421    if (!component_extension_ime_manager_->IsComponentExtensionIMEId(
422        unfiltered_input_method_ids[i])) {
423      // Legacy IMEs or xkb layouts are alwayes active.
424      active_input_method_ids_.push_back(unfiltered_input_method_ids[i]);
425    } else if (component_extension_ime_manager_->IsWhitelisted(
426        unfiltered_input_method_ids[i])) {
427      component_extension_ime_manager_->LoadComponentExtensionIME(
428          unfiltered_input_method_ids[i]);
429      active_input_method_ids_.push_back(unfiltered_input_method_ids[i]);
430    }
431  }
432}
433
434void InputMethodManagerImpl::ActivateInputMethodProperty(
435    const std::string& key) {
436  DCHECK(!key.empty());
437  ibus_controller_->ActivateInputMethodProperty(key);
438}
439
440void InputMethodManagerImpl::AddInputMethodExtension(
441    const std::string& id,
442    const std::string& name,
443    const std::vector<std::string>& layouts,
444    const std::string& language,
445    const GURL& options_url,
446    InputMethodEngine* engine) {
447  if (state_ == STATE_TERMINATING)
448    return;
449
450  if (!extension_ime_util::IsExtensionIME(id) &&
451      !ComponentExtensionIMEManager::IsComponentExtensionIMEId(id)) {
452    DVLOG(1) << id << " is not a valid extension input method ID.";
453    return;
454  }
455
456  extra_input_methods_[id] =
457      InputMethodDescriptor(id, name, layouts, language, options_url);
458  if (Contains(enabled_extension_imes_, id) &&
459      !ComponentExtensionIMEManager::IsComponentExtensionIMEId(id)) {
460    if (!Contains(active_input_method_ids_, id)) {
461      active_input_method_ids_.push_back(id);
462    } else {
463      DVLOG(1) << "AddInputMethodExtension: alread added: "
464               << id << ", " << name;
465      // Call Start() anyway, just in case.
466    }
467
468    // Ensure that the input method daemon is running.
469    MaybeInitializeCandidateWindowController();
470    IBusDaemonController::GetInstance()->Start();
471  }
472
473  extra_input_method_instances_[id] =
474      static_cast<InputMethodEngineIBus*>(engine);
475}
476
477void InputMethodManagerImpl::RemoveInputMethodExtension(const std::string& id) {
478  if (!extension_ime_util::IsExtensionIME(id))
479    DVLOG(1) << id << " is not a valid extension input method ID.";
480
481  std::vector<std::string>::iterator i = std::find(
482      active_input_method_ids_.begin(), active_input_method_ids_.end(), id);
483  if (i != active_input_method_ids_.end())
484    active_input_method_ids_.erase(i);
485  extra_input_methods_.erase(id);
486
487  if (ContainOnlyKeyboardLayout(active_input_method_ids_)) {
488    // Do NOT call ibus_controller_->Stop(); here to work around a crash issue
489    // at crosbug.com/27051.
490    // TODO(yusukes): We can safely call Stop(); here once crosbug.com/26443
491    // is implemented.
492  }
493
494  // If |current_input_method| is no longer in |active_input_method_ids_|,
495  // switch to the first one in |active_input_method_ids_|.
496  ChangeInputMethod(current_input_method_.id());
497
498  std::map<std::string, InputMethodEngineIBus*>::iterator ite =
499      extra_input_method_instances_.find(id);
500  if (ite == extra_input_method_instances_.end()) {
501    DVLOG(1) << "The engine instance of " << id << " has already gone.";
502  } else {
503    // Do NOT release the actual instance here. This class does not take an
504    // onwership of engine instance.
505    extra_input_method_instances_.erase(ite);
506  }
507}
508
509void InputMethodManagerImpl::GetInputMethodExtensions(
510    InputMethodDescriptors* result) {
511  // Build the extension input method descriptors from the extra input
512  // methods cache |extra_input_methods_|.
513  std::map<std::string, InputMethodDescriptor>::iterator iter;
514  for (iter = extra_input_methods_.begin(); iter != extra_input_methods_.end();
515       ++iter) {
516    if (extension_ime_util::IsExtensionIME(iter->first))
517      result->push_back(iter->second);
518  }
519}
520
521void InputMethodManagerImpl::SetEnabledExtensionImes(
522    std::vector<std::string>* ids) {
523  enabled_extension_imes_.clear();
524  enabled_extension_imes_.insert(enabled_extension_imes_.end(),
525                                 ids->begin(),
526                                 ids->end());
527
528  bool active_imes_changed = false;
529
530  for (std::map<std::string, InputMethodDescriptor>::iterator extra_iter =
531       extra_input_methods_.begin(); extra_iter != extra_input_methods_.end();
532       ++extra_iter) {
533    if (ComponentExtensionIMEManager::IsComponentExtensionIMEId(
534        extra_iter->first))
535      continue;  // Do not filter component extension.
536    std::vector<std::string>::iterator active_iter = std::find(
537        active_input_method_ids_.begin(), active_input_method_ids_.end(),
538        extra_iter->first);
539
540    bool active = active_iter != active_input_method_ids_.end();
541    bool enabled = Contains(enabled_extension_imes_, extra_iter->first);
542
543    if (active && !enabled)
544      active_input_method_ids_.erase(active_iter);
545
546    if (!active && enabled)
547      active_input_method_ids_.push_back(extra_iter->first);
548
549    if (active == !enabled)
550      active_imes_changed = true;
551  }
552
553  if (active_imes_changed) {
554    MaybeInitializeCandidateWindowController();
555    IBusDaemonController::GetInstance()->Start();
556
557    // If |current_input_method| is no longer in |active_input_method_ids_|,
558    // switch to the first one in |active_input_method_ids_|.
559    ChangeInputMethod(current_input_method_.id());
560  }
561}
562
563bool InputMethodManagerImpl::SwitchToNextInputMethod() {
564  // Sanity checks.
565  if (active_input_method_ids_.empty()) {
566    DVLOG(1) << "active input method is empty";
567    return false;
568  }
569  if (current_input_method_.id().empty()) {
570    DVLOG(1) << "current_input_method_ is unknown";
571    return false;
572  }
573
574  // Do not consume key event if there is only one input method is enabled.
575  // Ctrl+Space or Alt+Shift may be used by other application.
576  if (active_input_method_ids_.size() == 1)
577    return false;
578
579  // Find the next input method and switch to it.
580  SwitchToNextInputMethodInternal(active_input_method_ids_,
581                                  current_input_method_.id());
582  return true;
583}
584
585bool InputMethodManagerImpl::SwitchToPreviousInputMethod() {
586  // Sanity check.
587  if (active_input_method_ids_.empty()) {
588    DVLOG(1) << "active input method is empty";
589    return false;
590  }
591
592  // Do not consume key event if there is only one input method is enabled.
593  // Ctrl+Space or Alt+Shift may be used by other application.
594  if (active_input_method_ids_.size() == 1)
595    return false;
596
597  if (previous_input_method_.id().empty() ||
598      previous_input_method_.id() == current_input_method_.id()) {
599    return SwitchToNextInputMethod();
600  }
601
602  std::vector<std::string>::const_iterator iter =
603      std::find(active_input_method_ids_.begin(),
604                active_input_method_ids_.end(),
605                previous_input_method_.id());
606  if (iter == active_input_method_ids_.end()) {
607    // previous_input_method_ is not supported.
608    return SwitchToNextInputMethod();
609  }
610  ChangeInputMethodInternal(*iter, true);
611  return true;
612}
613
614bool InputMethodManagerImpl::SwitchInputMethod(
615    const ui::Accelerator& accelerator) {
616  // Sanity check.
617  if (active_input_method_ids_.empty()) {
618    DVLOG(1) << "active input method is empty";
619    return false;
620  }
621
622  // Get the list of input method ids for the |accelerator|. For example, get
623  // { "mozc-hangul", "xkb:kr:kr104:kor" } for ui::VKEY_DBE_SBCSCHAR.
624  std::vector<std::string> input_method_ids_to_switch;
625  switch (accelerator.key_code()) {
626    case ui::VKEY_CONVERT:  // Henkan key on JP106 keyboard
627      input_method_ids_to_switch.push_back(nacl_mozc_jp_id);
628      break;
629    case ui::VKEY_NONCONVERT:  // Muhenkan key on JP106 keyboard
630      input_method_ids_to_switch.push_back("xkb:jp::jpn");
631      break;
632    case ui::VKEY_DBE_SBCSCHAR:  // ZenkakuHankaku key on JP106 keyboard
633    case ui::VKEY_DBE_DBCSCHAR:
634      input_method_ids_to_switch.push_back(nacl_mozc_jp_id);
635      input_method_ids_to_switch.push_back("xkb:jp::jpn");
636      break;
637    case ui::VKEY_HANGUL:  // Hangul (or right Alt) key on Korean keyboard
638      input_method_ids_to_switch.push_back("mozc-hangul");
639      input_method_ids_to_switch.push_back("xkb:kr:kr104:kor");
640      break;
641    default:
642      NOTREACHED();
643      break;
644  }
645  if (input_method_ids_to_switch.empty()) {
646    DVLOG(1) << "Unexpected VKEY: " << accelerator.key_code();
647    return false;
648  }
649
650  // Obtain the intersection of input_method_ids_to_switch and
651  // active_input_method_ids_. The order of IDs in active_input_method_ids_ is
652  // preserved.
653  std::vector<std::string> ids;
654  for (size_t i = 0; i < input_method_ids_to_switch.size(); ++i) {
655    const std::string& id = input_method_ids_to_switch[i];
656    if (Contains(active_input_method_ids_, id))
657      ids.push_back(id);
658  }
659  if (ids.empty()) {
660    // No input method for the accelerator is active. For example, we should
661    // just ignore VKEY_HANGUL when mozc-hangul is not active.
662    return false;
663  }
664
665  SwitchToNextInputMethodInternal(ids, current_input_method_.id());
666  return true;  // consume the accelerator.
667}
668
669void InputMethodManagerImpl::SwitchToNextInputMethodInternal(
670    const std::vector<std::string>& input_method_ids,
671    const std::string& current_input_method_id) {
672  std::vector<std::string>::const_iterator iter =
673      std::find(input_method_ids.begin(),
674                input_method_ids.end(),
675                current_input_method_id);
676  if (iter != input_method_ids.end())
677    ++iter;
678  if (iter == input_method_ids.end())
679    iter = input_method_ids.begin();
680  ChangeInputMethodInternal(*iter, true);
681}
682
683InputMethodDescriptor InputMethodManagerImpl::GetCurrentInputMethod() const {
684  if (current_input_method_.id().empty())
685    return InputMethodUtil::GetFallbackInputMethodDescriptor();
686  return current_input_method_;
687}
688
689InputMethodPropertyList
690InputMethodManagerImpl::GetCurrentInputMethodProperties() const {
691  // This check is necessary since an IME property (e.g. for Pinyin) might be
692  // sent from ibus-daemon AFTER the current input method is switched to XKB.
693  if (InputMethodUtil::IsKeyboardLayout(GetCurrentInputMethod().id()))
694    return InputMethodPropertyList();
695  return ibus_controller_->GetCurrentProperties();
696}
697
698XKeyboard* InputMethodManagerImpl::GetXKeyboard() {
699  return xkeyboard_.get();
700}
701
702InputMethodUtil* InputMethodManagerImpl::GetInputMethodUtil() {
703  return &util_;
704}
705
706ComponentExtensionIMEManager*
707    InputMethodManagerImpl::GetComponentExtensionIMEManager() {
708  DCHECK(thread_checker_.CalledOnValidThread());
709  return component_extension_ime_manager_.get();
710}
711
712void InputMethodManagerImpl::OnConnected() {
713  for (std::map<std::string, InputMethodEngineIBus*>::iterator ite =
714          extra_input_method_instances_.begin();
715       ite != extra_input_method_instances_.end();
716       ite++) {
717    if (Contains(enabled_extension_imes_, ite->first) ||
718        (component_extension_ime_manager_->IsInitialized() &&
719         component_extension_ime_manager_->IsWhitelisted(ite->first))) {
720      ite->second->OnConnected();
721    }
722  }
723
724  if (!pending_input_method_.empty())
725    ChangeInputMethodInternal(pending_input_method_, false);
726}
727
728void InputMethodManagerImpl::OnDisconnected() {
729  for (std::map<std::string, InputMethodEngineIBus*>::iterator ite =
730          extra_input_method_instances_.begin();
731       ite != extra_input_method_instances_.end();
732       ite++) {
733    if (Contains(enabled_extension_imes_, ite->first))
734      ite->second->OnDisconnected();
735  }
736}
737
738void InputMethodManagerImpl::InitializeComponentExtension() {
739  ComponentExtensionIMEManagerImpl* impl =
740      new ComponentExtensionIMEManagerImpl();
741  scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate(impl);
742  impl->InitializeAsync(base::Bind(
743                       &InputMethodManagerImpl::OnComponentExtensionInitialized,
744                       weak_ptr_factory_.GetWeakPtr(),
745                       base::Passed(&delegate)));
746}
747
748void InputMethodManagerImpl::Init(base::SequencedTaskRunner* ui_task_runner) {
749  DCHECK(!ibus_controller_.get());
750  DCHECK(thread_checker_.CalledOnValidThread());
751
752  ibus_controller_.reset(IBusController::Create());
753  xkeyboard_.reset(XKeyboard::Create());
754  ibus_controller_->AddObserver(this);
755
756  // We can't call impl->Initialize here, because file thread is not available
757  // at this moment.
758  ui_task_runner->PostTask(
759      FROM_HERE,
760      base::Bind(&InputMethodManagerImpl::InitializeComponentExtension,
761                 weak_ptr_factory_.GetWeakPtr()));
762}
763
764void InputMethodManagerImpl::SetIBusControllerForTesting(
765    IBusController* ibus_controller) {
766  ibus_controller_.reset(ibus_controller);
767  ibus_controller_->AddObserver(this);
768}
769
770void InputMethodManagerImpl::SetCandidateWindowControllerForTesting(
771    CandidateWindowController* candidate_window_controller) {
772  candidate_window_controller_.reset(candidate_window_controller);
773  candidate_window_controller_->Init();
774  candidate_window_controller_->AddObserver(this);
775}
776
777void InputMethodManagerImpl::SetXKeyboardForTesting(XKeyboard* xkeyboard) {
778  xkeyboard_.reset(xkeyboard);
779}
780
781void InputMethodManagerImpl::InitializeComponentExtensionForTesting(
782    scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate) {
783  OnComponentExtensionInitialized(delegate.Pass());
784}
785
786void InputMethodManagerImpl::PropertyChanged() {
787  FOR_EACH_OBSERVER(InputMethodManager::Observer,
788                    observers_,
789                    InputMethodPropertyChanged(this));
790}
791
792void InputMethodManagerImpl::CandidateWindowOpened() {
793  FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver,
794                    candidate_window_observers_,
795                    CandidateWindowOpened(this));
796}
797
798void InputMethodManagerImpl::CandidateWindowClosed() {
799  FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver,
800                    candidate_window_observers_,
801                    CandidateWindowClosed(this));
802}
803
804void InputMethodManagerImpl::OnScreenLocked() {
805  saved_previous_input_method_ = previous_input_method_;
806  saved_current_input_method_ = current_input_method_;
807  saved_active_input_method_ids_ = active_input_method_ids_;
808
809  const std::string hardware_keyboard_id = util_.GetHardwareInputMethodId();
810  // We'll add the hardware keyboard if it's not included in
811  // |active_input_method_list| so that the user can always use the hardware
812  // keyboard on the screen locker.
813  bool should_add_hardware_keyboard = true;
814
815  active_input_method_ids_.clear();
816  for (size_t i = 0; i < saved_active_input_method_ids_.size(); ++i) {
817    const std::string& input_method_id = saved_active_input_method_ids_[i];
818    // Skip if it's not a keyboard layout. Drop input methods including
819    // extension ones.
820    if (!InputMethodUtil::IsKeyboardLayout(input_method_id))
821      continue;
822    active_input_method_ids_.push_back(input_method_id);
823    if (input_method_id == hardware_keyboard_id)
824      should_add_hardware_keyboard = false;
825  }
826  if (should_add_hardware_keyboard)
827    active_input_method_ids_.push_back(hardware_keyboard_id);
828
829  ChangeInputMethod(current_input_method_.id());
830}
831
832void InputMethodManagerImpl::OnScreenUnlocked() {
833  previous_input_method_ = saved_previous_input_method_;
834  current_input_method_ = saved_current_input_method_;
835  active_input_method_ids_ = saved_active_input_method_ids_;
836
837  ChangeInputMethod(current_input_method_.id());
838}
839
840bool InputMethodManagerImpl::InputMethodIsActivated(
841    const std::string& input_method_id) {
842  return Contains(active_input_method_ids_, input_method_id);
843}
844
845bool InputMethodManagerImpl::ContainOnlyKeyboardLayout(
846    const std::vector<std::string>& value) {
847  for (size_t i = 0; i < value.size(); ++i) {
848    if (!InputMethodUtil::IsKeyboardLayout(value[i]))
849      return false;
850  }
851  return true;
852}
853
854void InputMethodManagerImpl::MaybeInitializeCandidateWindowController() {
855  if (candidate_window_controller_.get())
856    return;
857
858  candidate_window_controller_.reset(
859      CandidateWindowController::CreateCandidateWindowController());
860  if (candidate_window_controller_->Init())
861    candidate_window_controller_->AddObserver(this);
862  else
863    DVLOG(1) << "Failed to initialize the candidate window controller";
864}
865
866bool InputMethodManagerImpl::IsIBusConnectionAlive() {
867  return DBusThreadManager::Get() && DBusThreadManager::Get()->GetIBusClient();
868}
869
870}  // namespace input_method
871}  // namespace chromeos
872