input_method_manager_impl.cc revision a93a17c8d99d686bd4a1511e5504e5e6cc9fcadf
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/string_util.h"
14#include "base/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  return rewritten;
259}
260
261bool InputMethodManagerImpl::SetInputMethodConfig(
262    const std::string& section,
263    const std::string& config_name,
264    const InputMethodConfigValue& value) {
265  DCHECK(section != language_prefs::kGeneralSectionName ||
266         config_name != language_prefs::kPreloadEnginesConfigName);
267
268  if (state_ == STATE_TERMINATING)
269    return false;
270  return ibus_controller_->SetInputMethodConfig(section, config_name, value);
271}
272
273void InputMethodManagerImpl::ChangeInputMethod(
274    const std::string& input_method_id) {
275  ChangeInputMethodInternal(input_method_id, false);
276}
277
278bool InputMethodManagerImpl::ChangeInputMethodInternal(
279    const std::string& input_method_id,
280    bool show_message) {
281  if (state_ == STATE_TERMINATING)
282    return false;
283
284  if (!component_extension_ime_manager_->IsInitialized() ||
285      (!InputMethodUtil::IsKeyboardLayout(input_method_id) &&
286       !IsIBusConnectionAlive())) {
287    // We can't change input method before the initialization of component
288    // extension ime manager or before connection to ibus-daemon is not
289    // established. ChangeInputMethod will be called with
290    // |pending_input_method_| when the both initialization is done.
291    pending_input_method_ = input_method_id;
292    return false;
293  }
294
295  std::string input_method_id_to_switch = input_method_id;
296
297  // Sanity check.
298  if (!InputMethodIsActivated(input_method_id)) {
299    scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods());
300    DCHECK(!input_methods->empty());
301    input_method_id_to_switch = input_methods->at(0).id();
302    if (!input_method_id.empty()) {
303      DVLOG(1) << "Can't change the current input method to "
304               << input_method_id << " since the engine is not enabled. "
305               << "Switch to " << input_method_id_to_switch << " instead.";
306    }
307  }
308
309  IBusInputContextClient* input_context =
310      chromeos::DBusThreadManager::Get()->GetIBusInputContextClient();
311  const std::string current_input_method_id = current_input_method_.id();
312  IBusClient* client = DBusThreadManager::Get()->GetIBusClient();
313  if (InputMethodUtil::IsKeyboardLayout(input_method_id_to_switch)) {
314    FOR_EACH_OBSERVER(InputMethodManager::Observer,
315                      observers_,
316                      InputMethodPropertyChanged(this));
317    // Hack for fixing http://crosbug.com/p/12798
318    // We should notify IME switching to ibus-daemon, otherwise
319    // IBusPreeditFocusMode does not work. To achieve it, change engine to
320    // itself if the next engine is XKB layout.
321    if (current_input_method_id.empty() ||
322        InputMethodUtil::IsKeyboardLayout(current_input_method_id)) {
323      if (input_context)
324        input_context->Reset();
325    } else {
326      if (client)
327        client->SetGlobalEngine(current_input_method_id,
328                                base::Bind(&base::DoNothing));
329    }
330    if (input_context)
331      input_context->SetIsXKBLayout(true);
332  } else {
333    DCHECK(client);
334    client->SetGlobalEngine(input_method_id_to_switch,
335                            base::Bind(&base::DoNothing));
336    if (input_context)
337      input_context->SetIsXKBLayout(false);
338  }
339
340  if (current_input_method_id != input_method_id_to_switch) {
341    // Clear input method properties unconditionally if
342    // |input_method_id_to_switch| is not equal to |current_input_method_id|.
343    //
344    // When switching to another input method and no text area is focused,
345    // RegisterProperties signal for the new input method will NOT be sent
346    // until a text area is focused. Therefore, we have to clear the old input
347    // method properties here to keep the input method switcher status
348    // consistent.
349    //
350    // When |input_method_id_to_switch| and |current_input_method_id| are the
351    // same, the properties shouldn't be cleared. If we do that, something
352    // wrong happens in step #4 below:
353    // 1. Enable "xkb:us::eng" and "mozc". Switch to "mozc".
354    // 2. Focus Omnibox. IME properties for mozc are sent to Chrome.
355    // 3. Switch to "xkb:us::eng". No function in this file is called.
356    // 4. Switch back to "mozc". ChangeInputMethod("mozc") is called, but it's
357    //    basically NOP since ibus-daemon's current IME is already "mozc".
358    //    IME properties are not sent to Chrome for the same reason.
359    // TODO(nona): Revisit above comment once ibus-daemon is gone.
360    ibus_controller_->ClearProperties();
361
362    const InputMethodDescriptor* descriptor = NULL;
363    if (!extension_ime_util::IsExtensionIME(input_method_id_to_switch)) {
364      descriptor =
365          util_.GetInputMethodDescriptorFromId(input_method_id_to_switch);
366    } else {
367      std::map<std::string, InputMethodDescriptor>::const_iterator i =
368          extra_input_methods_.find(input_method_id_to_switch);
369      DCHECK(i != extra_input_methods_.end());
370      descriptor = &(i->second);
371    }
372    DCHECK(descriptor);
373
374    previous_input_method_ = current_input_method_;
375    current_input_method_ = *descriptor;
376  }
377
378  // Change the keyboard layout to a preferred layout for the input method.
379  if (!xkeyboard_->SetCurrentKeyboardLayoutByName(
380          current_input_method_.GetPreferredKeyboardLayout())) {
381    LOG(ERROR) << "Failed to change keyboard layout to "
382               << current_input_method_.GetPreferredKeyboardLayout();
383  }
384
385  // Update input method indicators (e.g. "US", "DV") in Chrome windows.
386  FOR_EACH_OBSERVER(InputMethodManager::Observer,
387                    observers_,
388                    InputMethodChanged(this, show_message));
389  return true;
390}
391
392void InputMethodManagerImpl::OnComponentExtensionInitialized(
393    scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate) {
394  DCHECK(thread_checker_.CalledOnValidThread());
395  component_extension_ime_manager_->Initialize(delegate.Pass());
396  util_.SetComponentExtensions(
397      component_extension_ime_manager_->GetAllIMEAsInputMethodDescriptor());
398
399  LoadNecessaryComponentExtensions();
400
401  if (!pending_input_method_.empty()) {
402    if (ChangeInputMethodInternal(pending_input_method_, false))
403      pending_input_method_.clear();
404  }
405
406}
407
408void InputMethodManagerImpl::LoadNecessaryComponentExtensions() {
409  if (!component_extension_ime_manager_->IsInitialized())
410    return;
411  // Load component extensions but also update |active_input_method_ids_| as
412  // some component extension IMEs may have been removed from the Chrome OS
413  // image. If specified component extension IME no longer exists, falling back
414  // to an existing IME.
415  std::vector<std::string> unfiltered_input_method_ids =
416      active_input_method_ids_;
417  active_input_method_ids_.clear();
418  for (size_t i = 0; i < unfiltered_input_method_ids.size(); ++i) {
419    if (!component_extension_ime_manager_->IsComponentExtensionIMEId(
420        unfiltered_input_method_ids[i])) {
421      // Legacy IMEs or xkb layouts are alwayes active.
422      active_input_method_ids_.push_back(unfiltered_input_method_ids[i]);
423    } else if (component_extension_ime_manager_->IsWhitelisted(
424        unfiltered_input_method_ids[i])) {
425      component_extension_ime_manager_->LoadComponentExtensionIME(
426          unfiltered_input_method_ids[i]);
427      active_input_method_ids_.push_back(unfiltered_input_method_ids[i]);
428    }
429  }
430}
431
432void InputMethodManagerImpl::ActivateInputMethodProperty(
433    const std::string& key) {
434  DCHECK(!key.empty());
435  ibus_controller_->ActivateInputMethodProperty(key);
436}
437
438void InputMethodManagerImpl::AddInputMethodExtension(
439    const std::string& id,
440    const std::string& name,
441    const std::vector<std::string>& layouts,
442    const std::string& language,
443    InputMethodEngine* engine) {
444  if (state_ == STATE_TERMINATING)
445    return;
446
447  if (!extension_ime_util::IsExtensionIME(id) &&
448      !ComponentExtensionIMEManager::IsComponentExtensionIMEId(id)) {
449    DVLOG(1) << id << " is not a valid extension input method ID.";
450    return;
451  }
452
453  // TODO(nona): Support options page for normal extension ime.
454  //             crbug.com/156283.
455  extra_input_methods_[id] =
456      InputMethodDescriptor(id, name, layouts, language, GURL());
457  if (Contains(enabled_extension_imes_, id) &&
458      !ComponentExtensionIMEManager::IsComponentExtensionIMEId(id)) {
459    if (!Contains(active_input_method_ids_, id)) {
460      active_input_method_ids_.push_back(id);
461    } else {
462      DVLOG(1) << "AddInputMethodExtension: alread added: "
463               << id << ", " << name;
464      // Call Start() anyway, just in case.
465    }
466
467    // Ensure that the input method daemon is running.
468    MaybeInitializeCandidateWindowController();
469    IBusDaemonController::GetInstance()->Start();
470  }
471
472  extra_input_method_instances_[id] =
473      static_cast<InputMethodEngineIBus*>(engine);
474}
475
476void InputMethodManagerImpl::RemoveInputMethodExtension(const std::string& id) {
477  if (!extension_ime_util::IsExtensionIME(id))
478    DVLOG(1) << id << " is not a valid extension input method ID.";
479
480  std::vector<std::string>::iterator i = std::find(
481      active_input_method_ids_.begin(), active_input_method_ids_.end(), id);
482  if (i != active_input_method_ids_.end())
483    active_input_method_ids_.erase(i);
484  extra_input_methods_.erase(id);
485
486  if (ContainOnlyKeyboardLayout(active_input_method_ids_)) {
487    // Do NOT call ibus_controller_->Stop(); here to work around a crash issue
488    // at crosbug.com/27051.
489    // TODO(yusukes): We can safely call Stop(); here once crosbug.com/26443
490    // is implemented.
491  }
492
493  // If |current_input_method| is no longer in |active_input_method_ids_|,
494  // switch to the first one in |active_input_method_ids_|.
495  ChangeInputMethod(current_input_method_.id());
496
497  std::map<std::string, InputMethodEngineIBus*>::iterator ite =
498      extra_input_method_instances_.find(id);
499  if (ite == extra_input_method_instances_.end()) {
500    DVLOG(1) << "The engine instance of " << id << " has already gone.";
501  } else {
502    // Do NOT release the actual instance here. This class does not take an
503    // onwership of engine instance.
504    extra_input_method_instances_.erase(ite);
505  }
506}
507
508void InputMethodManagerImpl::GetInputMethodExtensions(
509    InputMethodDescriptors* result) {
510  // Build the extension input method descriptors from the extra input
511  // methods cache |extra_input_methods_|.
512  std::map<std::string, InputMethodDescriptor>::iterator iter;
513  for (iter = extra_input_methods_.begin(); iter != extra_input_methods_.end();
514       ++iter) {
515    if (extension_ime_util::IsExtensionIME(iter->first))
516      result->push_back(iter->second);
517  }
518}
519
520void InputMethodManagerImpl::SetEnabledExtensionImes(
521    std::vector<std::string>* ids) {
522  enabled_extension_imes_.clear();
523  enabled_extension_imes_.insert(enabled_extension_imes_.end(),
524                                 ids->begin(),
525                                 ids->end());
526
527  bool active_imes_changed = false;
528
529  for (std::map<std::string, InputMethodDescriptor>::iterator extra_iter =
530       extra_input_methods_.begin(); extra_iter != extra_input_methods_.end();
531       ++extra_iter) {
532    if (ComponentExtensionIMEManager::IsComponentExtensionIMEId(
533        extra_iter->first))
534      continue;  // Do not filter component extension.
535    std::vector<std::string>::iterator active_iter = std::find(
536        active_input_method_ids_.begin(), active_input_method_ids_.end(),
537        extra_iter->first);
538
539    bool active = active_iter != active_input_method_ids_.end();
540    bool enabled = Contains(enabled_extension_imes_, extra_iter->first);
541
542    if (active && !enabled)
543      active_input_method_ids_.erase(active_iter);
544
545    if (!active && enabled)
546      active_input_method_ids_.push_back(extra_iter->first);
547
548    if (active == !enabled)
549      active_imes_changed = true;
550  }
551
552  if (active_imes_changed) {
553    MaybeInitializeCandidateWindowController();
554    IBusDaemonController::GetInstance()->Start();
555
556    // If |current_input_method| is no longer in |active_input_method_ids_|,
557    // switch to the first one in |active_input_method_ids_|.
558    ChangeInputMethod(current_input_method_.id());
559  }
560}
561
562bool InputMethodManagerImpl::SwitchToNextInputMethod() {
563  // Sanity checks.
564  if (active_input_method_ids_.empty()) {
565    DVLOG(1) << "active input method is empty";
566    return false;
567  }
568  if (current_input_method_.id().empty()) {
569    DVLOG(1) << "current_input_method_ is unknown";
570    return false;
571  }
572
573  // Do not consume key event if there is only one input method is enabled.
574  // Ctrl+Space or Alt+Shift may be used by other application.
575  if (active_input_method_ids_.size() == 1)
576    return false;
577
578  // Find the next input method and switch to it.
579  SwitchToNextInputMethodInternal(active_input_method_ids_,
580                                  current_input_method_.id());
581  return true;
582}
583
584bool InputMethodManagerImpl::SwitchToPreviousInputMethod() {
585  // Sanity check.
586  if (active_input_method_ids_.empty()) {
587    DVLOG(1) << "active input method is empty";
588    return false;
589  }
590
591  // Do not consume key event if there is only one input method is enabled.
592  // Ctrl+Space or Alt+Shift may be used by other application.
593  if (active_input_method_ids_.size() == 1)
594    return false;
595
596  if (previous_input_method_.id().empty() ||
597      previous_input_method_.id() == current_input_method_.id()) {
598    return SwitchToNextInputMethod();
599  }
600
601  std::vector<std::string>::const_iterator iter =
602      std::find(active_input_method_ids_.begin(),
603                active_input_method_ids_.end(),
604                previous_input_method_.id());
605  if (iter == active_input_method_ids_.end()) {
606    // previous_input_method_ is not supported.
607    return SwitchToNextInputMethod();
608  }
609  ChangeInputMethodInternal(*iter, true);
610  return true;
611}
612
613bool InputMethodManagerImpl::SwitchInputMethod(
614    const ui::Accelerator& accelerator) {
615  // Sanity check.
616  if (active_input_method_ids_.empty()) {
617    DVLOG(1) << "active input method is empty";
618    return false;
619  }
620
621  // Get the list of input method ids for the |accelerator|. For example, get
622  // { "mozc-hangul", "xkb:kr:kr104:kor" } for ui::VKEY_DBE_SBCSCHAR.
623  std::vector<std::string> input_method_ids_to_switch;
624  switch (accelerator.key_code()) {
625    case ui::VKEY_CONVERT:  // Henkan key on JP106 keyboard
626      input_method_ids_to_switch.push_back(nacl_mozc_jp_id);
627      break;
628    case ui::VKEY_NONCONVERT:  // Muhenkan key on JP106 keyboard
629      input_method_ids_to_switch.push_back("xkb:jp::jpn");
630      break;
631    case ui::VKEY_DBE_SBCSCHAR:  // ZenkakuHankaku key on JP106 keyboard
632    case ui::VKEY_DBE_DBCSCHAR:
633      input_method_ids_to_switch.push_back(nacl_mozc_jp_id);
634      input_method_ids_to_switch.push_back("xkb:jp::jpn");
635      break;
636    case ui::VKEY_HANGUL:  // Hangul (or right Alt) key on Korean keyboard
637      input_method_ids_to_switch.push_back("mozc-hangul");
638      input_method_ids_to_switch.push_back("xkb:kr:kr104:kor");
639      break;
640    default:
641      NOTREACHED();
642      break;
643  }
644  if (input_method_ids_to_switch.empty()) {
645    DVLOG(1) << "Unexpected VKEY: " << accelerator.key_code();
646    return false;
647  }
648
649  // Obtain the intersection of input_method_ids_to_switch and
650  // active_input_method_ids_. The order of IDs in active_input_method_ids_ is
651  // preserved.
652  std::vector<std::string> ids;
653  for (size_t i = 0; i < input_method_ids_to_switch.size(); ++i) {
654    const std::string& id = input_method_ids_to_switch[i];
655    if (Contains(active_input_method_ids_, id))
656      ids.push_back(id);
657  }
658  if (ids.empty()) {
659    // No input method for the accelerator is active. For example, we should
660    // just ignore VKEY_HANGUL when mozc-hangul is not active.
661    return false;
662  }
663
664  SwitchToNextInputMethodInternal(ids, current_input_method_.id());
665  return true;  // consume the accelerator.
666}
667
668void InputMethodManagerImpl::SwitchToNextInputMethodInternal(
669    const std::vector<std::string>& input_method_ids,
670    const std::string& current_input_method_id) {
671  std::vector<std::string>::const_iterator iter =
672      std::find(input_method_ids.begin(),
673                input_method_ids.end(),
674                current_input_method_id);
675  if (iter != input_method_ids.end())
676    ++iter;
677  if (iter == input_method_ids.end())
678    iter = input_method_ids.begin();
679  ChangeInputMethodInternal(*iter, true);
680}
681
682InputMethodDescriptor InputMethodManagerImpl::GetCurrentInputMethod() const {
683  if (current_input_method_.id().empty())
684    return InputMethodUtil::GetFallbackInputMethodDescriptor();
685  return current_input_method_;
686}
687
688InputMethodPropertyList
689InputMethodManagerImpl::GetCurrentInputMethodProperties() const {
690  // This check is necessary since an IME property (e.g. for Pinyin) might be
691  // sent from ibus-daemon AFTER the current input method is switched to XKB.
692  if (InputMethodUtil::IsKeyboardLayout(GetCurrentInputMethod().id()))
693    return InputMethodPropertyList();
694  return ibus_controller_->GetCurrentProperties();
695}
696
697XKeyboard* InputMethodManagerImpl::GetXKeyboard() {
698  return xkeyboard_.get();
699}
700
701InputMethodUtil* InputMethodManagerImpl::GetInputMethodUtil() {
702  return &util_;
703}
704
705ComponentExtensionIMEManager*
706    InputMethodManagerImpl::GetComponentExtensionIMEManager() {
707  DCHECK(thread_checker_.CalledOnValidThread());
708  return component_extension_ime_manager_.get();
709}
710
711void InputMethodManagerImpl::OnConnected() {
712  for (std::map<std::string, InputMethodEngineIBus*>::iterator ite =
713          extra_input_method_instances_.begin();
714       ite != extra_input_method_instances_.end();
715       ite++) {
716    if (Contains(enabled_extension_imes_, ite->first) ||
717        (component_extension_ime_manager_->IsInitialized() &&
718         component_extension_ime_manager_->IsWhitelisted(ite->first))) {
719      ite->second->OnConnected();
720    }
721  }
722
723  if (!pending_input_method_.empty()) {
724    if (ChangeInputMethodInternal(pending_input_method_, false))
725      pending_input_method_.clear();
726  }
727}
728
729void InputMethodManagerImpl::OnDisconnected() {
730  for (std::map<std::string, InputMethodEngineIBus*>::iterator ite =
731          extra_input_method_instances_.begin();
732       ite != extra_input_method_instances_.end();
733       ite++) {
734    if (Contains(enabled_extension_imes_, ite->first))
735      ite->second->OnDisconnected();
736  }
737}
738
739void InputMethodManagerImpl::InitializeComponentExtension() {
740  ComponentExtensionIMEManagerImpl* impl =
741      new ComponentExtensionIMEManagerImpl();
742  scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate(impl);
743  impl->InitializeAsync(base::Bind(
744                       &InputMethodManagerImpl::OnComponentExtensionInitialized,
745                       weak_ptr_factory_.GetWeakPtr(),
746                       base::Passed(&delegate)));
747}
748
749void InputMethodManagerImpl::Init(base::SequencedTaskRunner* ui_task_runner) {
750  DCHECK(!ibus_controller_.get());
751  DCHECK(thread_checker_.CalledOnValidThread());
752
753  ibus_controller_.reset(IBusController::Create());
754  xkeyboard_.reset(XKeyboard::Create());
755  ibus_controller_->AddObserver(this);
756
757  // We can't call impl->Initialize here, because file thread is not available
758  // at this moment.
759  ui_task_runner->PostTask(
760      FROM_HERE,
761      base::Bind(&InputMethodManagerImpl::InitializeComponentExtension,
762                 weak_ptr_factory_.GetWeakPtr()));
763}
764
765void InputMethodManagerImpl::SetIBusControllerForTesting(
766    IBusController* ibus_controller) {
767  ibus_controller_.reset(ibus_controller);
768  ibus_controller_->AddObserver(this);
769}
770
771void InputMethodManagerImpl::SetCandidateWindowControllerForTesting(
772    CandidateWindowController* candidate_window_controller) {
773  candidate_window_controller_.reset(candidate_window_controller);
774  candidate_window_controller_->Init();
775  candidate_window_controller_->AddObserver(this);
776}
777
778void InputMethodManagerImpl::SetXKeyboardForTesting(XKeyboard* xkeyboard) {
779  xkeyboard_.reset(xkeyboard);
780}
781
782void InputMethodManagerImpl::InitializeComponentExtensionForTesting(
783    scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate) {
784  OnComponentExtensionInitialized(delegate.Pass());
785}
786
787void InputMethodManagerImpl::PropertyChanged() {
788  FOR_EACH_OBSERVER(InputMethodManager::Observer,
789                    observers_,
790                    InputMethodPropertyChanged(this));
791}
792
793void InputMethodManagerImpl::CandidateWindowOpened() {
794  FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver,
795                    candidate_window_observers_,
796                    CandidateWindowOpened(this));
797}
798
799void InputMethodManagerImpl::CandidateWindowClosed() {
800  FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver,
801                    candidate_window_observers_,
802                    CandidateWindowClosed(this));
803}
804
805void InputMethodManagerImpl::OnScreenLocked() {
806  saved_previous_input_method_ = previous_input_method_;
807  saved_current_input_method_ = current_input_method_;
808  saved_active_input_method_ids_ = active_input_method_ids_;
809
810  const std::string hardware_keyboard_id = util_.GetHardwareInputMethodId();
811  // We'll add the hardware keyboard if it's not included in
812  // |active_input_method_list| so that the user can always use the hardware
813  // keyboard on the screen locker.
814  bool should_add_hardware_keyboard = true;
815
816  active_input_method_ids_.clear();
817  for (size_t i = 0; i < saved_active_input_method_ids_.size(); ++i) {
818    const std::string& input_method_id = saved_active_input_method_ids_[i];
819    // Skip if it's not a keyboard layout. Drop input methods including
820    // extension ones.
821    if (!InputMethodUtil::IsKeyboardLayout(input_method_id))
822      continue;
823    active_input_method_ids_.push_back(input_method_id);
824    if (input_method_id == hardware_keyboard_id)
825      should_add_hardware_keyboard = false;
826  }
827  if (should_add_hardware_keyboard)
828    active_input_method_ids_.push_back(hardware_keyboard_id);
829
830  ChangeInputMethod(current_input_method_.id());
831}
832
833void InputMethodManagerImpl::OnScreenUnlocked() {
834  previous_input_method_ = saved_previous_input_method_;
835  current_input_method_ = saved_current_input_method_;
836  active_input_method_ids_ = saved_active_input_method_ids_;
837
838  ChangeInputMethod(current_input_method_.id());
839}
840
841bool InputMethodManagerImpl::InputMethodIsActivated(
842    const std::string& input_method_id) {
843  return Contains(active_input_method_ids_, input_method_id);
844}
845
846bool InputMethodManagerImpl::ContainOnlyKeyboardLayout(
847    const std::vector<std::string>& value) {
848  for (size_t i = 0; i < value.size(); ++i) {
849    if (!InputMethodUtil::IsKeyboardLayout(value[i]))
850      return false;
851  }
852  return true;
853}
854
855void InputMethodManagerImpl::MaybeInitializeCandidateWindowController() {
856  if (candidate_window_controller_.get())
857    return;
858
859  candidate_window_controller_.reset(
860      CandidateWindowController::CreateCandidateWindowController());
861  if (candidate_window_controller_->Init())
862    candidate_window_controller_->AddObserver(this);
863  else
864    DVLOG(1) << "Failed to initialize the candidate window controller";
865}
866
867bool InputMethodManagerImpl::IsIBusConnectionAlive() {
868  return DBusThreadManager::Get() && DBusThreadManager::Get()->GetIBusClient();
869}
870
871}  // namespace input_method
872}  // namespace chromeos
873