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 <sstream> 10 11 #include "ash/ime/input_method_menu_item.h" 12 #include "ash/ime/input_method_menu_manager.h" 13 #include "base/basictypes.h" 14 #include "base/bind.h" 15 #include "base/location.h" 16 #include "base/memory/scoped_ptr.h" 17 #include "base/metrics/histogram.h" 18 #include "base/metrics/sparse_histogram.h" 19 #include "base/prefs/pref_service.h" 20 #include "base/strings/string_split.h" 21 #include "base/strings/string_util.h" 22 #include "base/strings/stringprintf.h" 23 #include "base/sys_info.h" 24 #include "chrome/browser/browser_process.h" 25 #include "chrome/browser/chromeos/input_method/candidate_window_controller.h" 26 #include "chrome/browser/chromeos/input_method/component_extension_ime_manager_impl.h" 27 #include "chrome/browser/chromeos/input_method/input_method_engine.h" 28 #include "chrome/browser/chromeos/language_preferences.h" 29 #include "chrome/browser/chromeos/login/session/user_session_manager.h" 30 #include "chrome/browser/chromeos/profiles/profile_helper.h" 31 #include "chrome/browser/profiles/profile_manager.h" 32 #include "chrome/common/pref_names.h" 33 #include "chromeos/ime/component_extension_ime_manager.h" 34 #include "chromeos/ime/extension_ime_util.h" 35 #include "chromeos/ime/fake_ime_keyboard.h" 36 #include "chromeos/ime/ime_keyboard.h" 37 #include "chromeos/ime/input_method_delegate.h" 38 #include "components/user_manager/user_manager.h" 39 #include "third_party/icu/source/common/unicode/uloc.h" 40 #include "ui/base/accelerators/accelerator.h" 41 #include "ui/keyboard/keyboard_controller.h" 42 #include "ui/keyboard/keyboard_util.h" 43 44 namespace chromeos { 45 namespace input_method { 46 47 namespace { 48 49 bool Contains(const std::vector<std::string>& container, 50 const std::string& value) { 51 return std::find(container.begin(), container.end(), value) != 52 container.end(); 53 } 54 55 enum InputMethodCategory { 56 INPUT_METHOD_CATEGORY_UNKNOWN = 0, 57 INPUT_METHOD_CATEGORY_XKB, // XKB input methods 58 INPUT_METHOD_CATEGORY_ZH, // Chinese input methods 59 INPUT_METHOD_CATEGORY_JA, // Japanese input methods 60 INPUT_METHOD_CATEGORY_KO, // Korean input methods 61 INPUT_METHOD_CATEGORY_M17N, // Multilingualization input methods 62 INPUT_METHOD_CATEGORY_T13N, // Transliteration input methods 63 INPUT_METHOD_CATEGORY_MAX 64 }; 65 66 InputMethodCategory GetInputMethodCategory(const std::string& input_method_id, 67 char* first_char = NULL) { 68 const std::string component_id = 69 extension_ime_util::GetComponentIDByInputMethodID(input_method_id); 70 InputMethodCategory category = INPUT_METHOD_CATEGORY_UNKNOWN; 71 char ch = 0; 72 if (StartsWithASCII(component_id, "xkb:", true)) { 73 ch = component_id[4]; 74 category = INPUT_METHOD_CATEGORY_XKB; 75 } else if (StartsWithASCII(component_id, "zh-", true)) { 76 size_t pos = component_id.find("-t-i0-"); 77 if (pos > 0) 78 pos += 6; 79 ch = component_id[pos]; 80 category = INPUT_METHOD_CATEGORY_ZH; 81 } else if (StartsWithASCII(component_id, "nacl_mozc_", true)) { 82 ch = component_id[10]; 83 category = INPUT_METHOD_CATEGORY_JA; 84 } else if (StartsWithASCII(component_id, "hangul_", true)) { 85 ch = component_id[7]; 86 category = INPUT_METHOD_CATEGORY_KO; 87 } else if (StartsWithASCII(component_id, "vkd_", true)) { 88 ch = component_id[4]; 89 category = INPUT_METHOD_CATEGORY_M17N; 90 } else if (component_id.find("-t-i0-") > 0) { 91 ch = component_id[0]; 92 category = INPUT_METHOD_CATEGORY_T13N; 93 } 94 95 if (first_char) 96 *first_char = ch; 97 return category; 98 } 99 100 } // namespace 101 102 // ------------------------ InputMethodManagerImpl::StateImpl 103 104 InputMethodManagerImpl::StateImpl::StateImpl(InputMethodManagerImpl* manager, 105 Profile* profile) 106 : profile(profile), manager_(manager) { 107 } 108 109 InputMethodManagerImpl::StateImpl::~StateImpl() { 110 } 111 112 void InputMethodManagerImpl::StateImpl::InitFrom(const StateImpl& other) { 113 previous_input_method = other.previous_input_method; 114 current_input_method = other.current_input_method; 115 116 active_input_method_ids = other.active_input_method_ids; 117 118 pending_input_method_id = other.pending_input_method_id; 119 120 enabled_extension_imes = other.enabled_extension_imes; 121 extra_input_methods = other.extra_input_methods; 122 } 123 124 bool InputMethodManagerImpl::StateImpl::IsActive() const { 125 return manager_->state_.get() == this; 126 } 127 128 std::string InputMethodManagerImpl::StateImpl::Dump() const { 129 std::ostringstream os; 130 131 os << "################# " 132 << (profile ? profile->GetProfileName() : std::string("NULL")) 133 << " #################\n"; 134 135 os << "previous_input_method: '" 136 << previous_input_method.GetPreferredKeyboardLayout() << "'\n"; 137 os << "current_input_method: '" 138 << current_input_method.GetPreferredKeyboardLayout() << "'\n"; 139 os << "active_input_method_ids (size=" << active_input_method_ids.size() 140 << "):"; 141 for (size_t i = 0; i < active_input_method_ids.size(); ++i) { 142 os << " '" << active_input_method_ids[i] << "',"; 143 } 144 os << "\n"; 145 os << "enabled_extension_imes (size=" << enabled_extension_imes.size() 146 << "):"; 147 for (size_t i = 0; i < enabled_extension_imes.size(); ++i) { 148 os << " '" << enabled_extension_imes[i] << "'\n"; 149 } 150 os << "\n"; 151 os << "extra_input_methods (size=" << extra_input_methods.size() << "):"; 152 for (std::map<std::string, InputMethodDescriptor>::const_iterator it = 153 extra_input_methods.begin(); 154 it != extra_input_methods.end(); 155 ++it) { 156 os << " '" << it->first << "' => '" << it->second.id() << "',\n"; 157 } 158 os << "pending_input_method_id: '" << pending_input_method_id << "'\n"; 159 160 return os.str(); 161 } 162 163 scoped_refptr<InputMethodManager::State> 164 InputMethodManagerImpl::StateImpl::Clone() const { 165 scoped_refptr<StateImpl> new_state(new StateImpl(this->manager_, profile)); 166 new_state->InitFrom(*this); 167 return scoped_refptr<InputMethodManager::State>(new_state.get()); 168 } 169 170 scoped_ptr<InputMethodDescriptors> 171 InputMethodManagerImpl::StateImpl::GetActiveInputMethods() const { 172 scoped_ptr<InputMethodDescriptors> result(new InputMethodDescriptors); 173 // Build the active input method descriptors from the active input 174 // methods cache |active_input_method_ids|. 175 for (size_t i = 0; i < active_input_method_ids.size(); ++i) { 176 const std::string& input_method_id = active_input_method_ids[i]; 177 const InputMethodDescriptor* descriptor = 178 manager_->util_.GetInputMethodDescriptorFromId(input_method_id); 179 if (descriptor) { 180 result->push_back(*descriptor); 181 } else { 182 std::map<std::string, InputMethodDescriptor>::const_iterator ix = 183 extra_input_methods.find(input_method_id); 184 if (ix != extra_input_methods.end()) 185 result->push_back(ix->second); 186 else 187 DVLOG(1) << "Descriptor is not found for: " << input_method_id; 188 } 189 } 190 if (result->empty()) { 191 // Initially |active_input_method_ids| is empty. browser_tests might take 192 // this path. 193 result->push_back( 194 InputMethodUtil::GetFallbackInputMethodDescriptor()); 195 } 196 return result.Pass(); 197 } 198 199 const std::vector<std::string>& 200 InputMethodManagerImpl::StateImpl::GetActiveInputMethodIds() const { 201 return active_input_method_ids; 202 } 203 204 size_t InputMethodManagerImpl::StateImpl::GetNumActiveInputMethods() const { 205 return active_input_method_ids.size(); 206 } 207 208 const InputMethodDescriptor* 209 InputMethodManagerImpl::StateImpl::GetInputMethodFromId( 210 const std::string& input_method_id) const { 211 const InputMethodDescriptor* ime = 212 manager_->util_.GetInputMethodDescriptorFromId(input_method_id); 213 if (!ime) { 214 std::map<std::string, InputMethodDescriptor>::const_iterator ix = 215 extra_input_methods.find(input_method_id); 216 if (ix != extra_input_methods.end()) 217 ime = &ix->second; 218 } 219 return ime; 220 } 221 222 void InputMethodManagerImpl::StateImpl::EnableLoginLayouts( 223 const std::string& language_code, 224 const std::vector<std::string>& initial_layouts) { 225 if (manager_->ui_session_ == STATE_TERMINATING) 226 return; 227 228 // First, hardware keyboard layout should be shown. 229 std::vector<std::string> candidates = 230 manager_->util_.GetHardwareLoginInputMethodIds(); 231 232 // Second, locale based input method should be shown. 233 // Add input methods associated with the language. 234 std::vector<std::string> layouts_from_locale; 235 manager_->util_.GetInputMethodIdsFromLanguageCode( 236 language_code, kKeyboardLayoutsOnly, &layouts_from_locale); 237 candidates.insert(candidates.end(), layouts_from_locale.begin(), 238 layouts_from_locale.end()); 239 240 std::vector<std::string> layouts; 241 // First, add the initial input method ID, if it's requested, to 242 // layouts, so it appears first on the list of active input 243 // methods at the input language status menu. 244 for (size_t i = 0; i < initial_layouts.size(); ++i) { 245 if (manager_->util_.IsValidInputMethodId(initial_layouts[i])) { 246 if (manager_->IsLoginKeyboard(initial_layouts[i])) { 247 layouts.push_back(initial_layouts[i]); 248 } else { 249 DVLOG(1) 250 << "EnableLoginLayouts: ignoring non-login initial keyboard layout:" 251 << initial_layouts[i]; 252 } 253 } else if (!initial_layouts[i].empty()) { 254 DVLOG(1) << "EnableLoginLayouts: ignoring non-keyboard or invalid ID: " 255 << initial_layouts[i]; 256 } 257 } 258 259 // Add candidates to layouts, while skipping duplicates. 260 for (size_t i = 0; i < candidates.size(); ++i) { 261 const std::string& candidate = candidates[i]; 262 // Not efficient, but should be fine, as the two vectors are very 263 // short (2-5 items). 264 if (!Contains(layouts, candidate) && manager_->IsLoginKeyboard(candidate)) 265 layouts.push_back(candidate); 266 } 267 268 manager_->MigrateInputMethods(&layouts); 269 active_input_method_ids.swap(layouts); 270 271 if (IsActive()) { 272 // Initialize candidate window controller and widgets such as 273 // candidate window, infolist and mode indicator. Note, mode 274 // indicator is used by only keyboard layout input methods. 275 if (active_input_method_ids.size() > 1) 276 manager_->MaybeInitializeCandidateWindowController(); 277 278 // you can pass empty |initial_layout|. 279 ChangeInputMethod(initial_layouts.empty() 280 ? std::string() 281 : extension_ime_util::GetInputMethodIDByEngineID( 282 initial_layouts[0]), 283 false); 284 } 285 } 286 287 void InputMethodManagerImpl::StateImpl::EnableLockScreenLayouts() { 288 std::set<std::string> added_ids; 289 290 const std::vector<std::string>& hardware_keyboard_ids = 291 manager_->util_.GetHardwareLoginInputMethodIds(); 292 293 std::vector<std::string> new_active_input_method_ids; 294 for (size_t i = 0; i < active_input_method_ids.size(); ++i) { 295 const std::string& input_method_id = active_input_method_ids[i]; 296 // Skip if it's not a keyboard layout. Drop input methods including 297 // extension ones. 298 if (!manager_->IsLoginKeyboard(input_method_id) || 299 added_ids.count(input_method_id)) { 300 continue; 301 } 302 new_active_input_method_ids.push_back(input_method_id); 303 added_ids.insert(input_method_id); 304 } 305 306 // We'll add the hardware keyboard if it's not included in 307 // |active_input_method_ids| so that the user can always use the hardware 308 // keyboard on the screen locker. 309 for (size_t i = 0; i < hardware_keyboard_ids.size(); ++i) { 310 if (added_ids.count(hardware_keyboard_ids[i])) 311 continue; 312 new_active_input_method_ids.push_back(hardware_keyboard_ids[i]); 313 added_ids.insert(hardware_keyboard_ids[i]); 314 } 315 316 active_input_method_ids.swap(new_active_input_method_ids); 317 318 // Re-check current_input_method. 319 ChangeInputMethod(current_input_method.id(), false); 320 } 321 322 // Adds new input method to given list. 323 bool InputMethodManagerImpl::StateImpl::EnableInputMethodImpl( 324 const std::string& input_method_id, 325 std::vector<std::string>* new_active_input_method_ids) const { 326 DCHECK(new_active_input_method_ids); 327 if (!manager_->util_.IsValidInputMethodId(input_method_id)) { 328 DVLOG(1) << "EnableInputMethod: Invalid ID: " << input_method_id; 329 return false; 330 } 331 332 if (!Contains(*new_active_input_method_ids, input_method_id)) 333 new_active_input_method_ids->push_back(input_method_id); 334 335 return true; 336 } 337 338 bool InputMethodManagerImpl::StateImpl::EnableInputMethod( 339 const std::string& input_method_id) { 340 if (!EnableInputMethodImpl(input_method_id, &active_input_method_ids)) 341 return false; 342 343 manager_->ReconfigureIMFramework(this); 344 return true; 345 } 346 347 bool InputMethodManagerImpl::StateImpl::ReplaceEnabledInputMethods( 348 const std::vector<std::string>& new_active_input_method_ids) { 349 if (manager_->ui_session_ == STATE_TERMINATING) 350 return false; 351 352 // Filter unknown or obsolete IDs. 353 std::vector<std::string> new_active_input_method_ids_filtered; 354 355 for (size_t i = 0; i < new_active_input_method_ids.size(); ++i) 356 EnableInputMethodImpl(new_active_input_method_ids[i], 357 &new_active_input_method_ids_filtered); 358 359 if (new_active_input_method_ids_filtered.empty()) { 360 DVLOG(1) << "ReplaceEnabledInputMethods: No valid input method ID"; 361 return false; 362 } 363 364 // Copy extension IDs to |new_active_input_method_ids_filtered|. We have to 365 // keep relative order of the extension input method IDs. 366 for (size_t i = 0; i < active_input_method_ids.size(); ++i) { 367 const std::string& input_method_id = active_input_method_ids[i]; 368 if (extension_ime_util::IsExtensionIME(input_method_id)) 369 new_active_input_method_ids_filtered.push_back(input_method_id); 370 } 371 active_input_method_ids.swap(new_active_input_method_ids_filtered); 372 manager_->MigrateInputMethods(&active_input_method_ids); 373 374 manager_->ReconfigureIMFramework(this); 375 376 // If |current_input_method| is no longer in |active_input_method_ids|, 377 // ChangeInputMethod() picks the first one in |active_input_method_ids|. 378 ChangeInputMethod(current_input_method.id(), false); 379 380 // Record histogram for active input method count. 381 UMA_HISTOGRAM_COUNTS("InputMethod.ActiveCount", 382 active_input_method_ids.size()); 383 384 return true; 385 } 386 387 void InputMethodManagerImpl::StateImpl::ChangeInputMethod( 388 const std::string& input_method_id, 389 bool show_message) { 390 if (manager_->ui_session_ == STATE_TERMINATING) 391 return; 392 393 bool notify_menu = false; 394 // For 3rd party IME, when the user just logged in, SetEnabledExtensionImes 395 // happens after activating the 3rd party IME. 396 // So here to record the 3rd party IME to be activated, and activate it 397 // when SetEnabledExtensionImes happens later. 398 if (MethodAwaitsExtensionLoad(input_method_id)) 399 pending_input_method_id = input_method_id; 400 401 // Always lookup input method, even if it is the same as 402 // |current_input_method| because If it is no longer in 403 // |active_input_method_ids|, pick the first one in 404 // |active_input_method_ids|. 405 const InputMethodDescriptor* descriptor = 406 manager_->LookupInputMethod(input_method_id, this); 407 if (descriptor->id() != current_input_method.id()) { 408 previous_input_method = current_input_method; 409 current_input_method = *descriptor; 410 notify_menu = true; 411 } 412 413 // Always change input method even if it is the same. 414 // TODO(komatsu): Revisit if this is neccessary. 415 if (IsActive()) 416 manager_->ChangeInputMethodInternal(*descriptor, show_message, notify_menu); 417 manager_->RecordInputMethodUsage(current_input_method.id()); 418 } 419 420 bool InputMethodManagerImpl::StateImpl::MethodAwaitsExtensionLoad( 421 const std::string& input_method_id) const { 422 // For 3rd party IME, when the user just logged in, SetEnabledExtensionImes 423 // happens after activating the 3rd party IME. 424 // So here to record the 3rd party IME to be activated, and activate it 425 // when SetEnabledExtensionImes happens later. 426 return !InputMethodIsActivated(input_method_id) && 427 extension_ime_util::IsExtensionIME(input_method_id); 428 } 429 430 void InputMethodManagerImpl::StateImpl::AddInputMethodExtension( 431 const std::string& extension_id, 432 const InputMethodDescriptors& descriptors, 433 InputMethodEngineInterface* engine) { 434 if (manager_->ui_session_ == STATE_TERMINATING) 435 return; 436 437 DCHECK(engine); 438 439 manager_->engine_map_[extension_id] = engine; 440 441 bool contain = false; 442 for (size_t i = 0; i < descriptors.size(); i++) { 443 const InputMethodDescriptor& descriptor = descriptors[i]; 444 const std::string& id = descriptor.id(); 445 extra_input_methods[id] = descriptor; 446 if (Contains(enabled_extension_imes, id)) { 447 if (!Contains(active_input_method_ids, id)) { 448 active_input_method_ids.push_back(id); 449 } else { 450 DVLOG(1) << "AddInputMethodExtension: already added: " << id << ", " 451 << descriptor.name(); 452 } 453 contain = true; 454 } 455 } 456 457 if (IsActive()) { 458 if (extension_id == extension_ime_util::GetExtensionIDFromInputMethodID( 459 current_input_method.id())) { 460 IMEBridge::Get()->SetCurrentEngineHandler(engine); 461 engine->Enable(extension_ime_util::GetComponentIDByInputMethodID( 462 current_input_method.id())); 463 } 464 465 // Ensure that the input method daemon is running. 466 if (contain) 467 manager_->MaybeInitializeCandidateWindowController(); 468 } 469 } 470 471 void InputMethodManagerImpl::StateImpl::RemoveInputMethodExtension( 472 const std::string& extension_id) { 473 // Remove the active input methods with |extension_id|. 474 std::vector<std::string> new_active_input_method_ids; 475 for (size_t i = 0; i < active_input_method_ids.size(); ++i) { 476 if (extension_id != extension_ime_util::GetExtensionIDFromInputMethodID( 477 active_input_method_ids[i])) 478 new_active_input_method_ids.push_back(active_input_method_ids[i]); 479 } 480 active_input_method_ids.swap(new_active_input_method_ids); 481 482 // Remove the extra input methods with |extension_id|. 483 std::map<std::string, InputMethodDescriptor> new_extra_input_methods; 484 for (std::map<std::string, InputMethodDescriptor>::iterator i = 485 extra_input_methods.begin(); 486 i != extra_input_methods.end(); 487 ++i) { 488 if (extension_id != 489 extension_ime_util::GetExtensionIDFromInputMethodID(i->first)) 490 new_extra_input_methods[i->first] = i->second; 491 } 492 extra_input_methods.swap(new_extra_input_methods); 493 494 if (IsActive()) { 495 if (IMEBridge::Get()->GetCurrentEngineHandler() == 496 manager_->engine_map_[extension_id]) { 497 IMEBridge::Get()->SetCurrentEngineHandler(NULL); 498 } 499 manager_->engine_map_.erase(extension_id); 500 } 501 502 // If |current_input_method| is no longer in |active_input_method_ids|, 503 // switch to the first one in |active_input_method_ids|. 504 ChangeInputMethod(current_input_method.id(), false); 505 } 506 507 void InputMethodManagerImpl::StateImpl::GetInputMethodExtensions( 508 InputMethodDescriptors* result) { 509 // Build the extension input method descriptors from the extra input 510 // methods cache |extra_input_methods|. 511 std::map<std::string, InputMethodDescriptor>::iterator iter; 512 for (iter = extra_input_methods.begin(); iter != extra_input_methods.end(); 513 ++iter) { 514 if (extension_ime_util::IsExtensionIME(iter->first)) 515 result->push_back(iter->second); 516 } 517 } 518 519 void InputMethodManagerImpl::StateImpl::SetEnabledExtensionImes( 520 std::vector<std::string>* ids) { 521 enabled_extension_imes.clear(); 522 enabled_extension_imes.insert( 523 enabled_extension_imes.end(), ids->begin(), ids->end()); 524 bool active_imes_changed = false; 525 bool switch_to_pending = false; 526 527 for (std::map<std::string, InputMethodDescriptor>::iterator extra_iter = 528 extra_input_methods.begin(); 529 extra_iter != extra_input_methods.end(); 530 ++extra_iter) { 531 if (extension_ime_util::IsComponentExtensionIME(extra_iter->first)) 532 continue; // Do not filter component extension. 533 534 if (pending_input_method_id == extra_iter->first) 535 switch_to_pending = true; 536 537 std::vector<std::string>::iterator active_iter = 538 std::find(active_input_method_ids.begin(), 539 active_input_method_ids.end(), 540 extra_iter->first); 541 542 bool active = active_iter != active_input_method_ids.end(); 543 bool enabled = Contains(enabled_extension_imes, extra_iter->first); 544 545 if (active && !enabled) 546 active_input_method_ids.erase(active_iter); 547 548 if (!active && enabled) 549 active_input_method_ids.push_back(extra_iter->first); 550 551 if (active == !enabled) 552 active_imes_changed = true; 553 } 554 555 if (IsActive() && active_imes_changed) { 556 manager_->MaybeInitializeCandidateWindowController(); 557 558 if (switch_to_pending) { 559 ChangeInputMethod(pending_input_method_id, false); 560 pending_input_method_id.clear(); 561 } else { 562 // If |current_input_method| is no longer in |active_input_method_ids_|, 563 // switch to the first one in |active_input_method_ids_|. 564 ChangeInputMethod(current_input_method.id(), false); 565 } 566 } 567 } 568 569 void InputMethodManagerImpl::StateImpl::SetInputMethodLoginDefaultFromVPD( 570 const std::string& locale, 571 const std::string& oem_layout) { 572 std::string layout; 573 if (!oem_layout.empty()) { 574 // If the OEM layout information is provided, use it. 575 layout = oem_layout; 576 } else { 577 // Otherwise, determine the hardware keyboard from the locale. 578 std::vector<std::string> input_method_ids; 579 if (manager_->util_.GetInputMethodIdsFromLanguageCode( 580 locale, 581 chromeos::input_method::kKeyboardLayoutsOnly, 582 &input_method_ids)) { 583 // The output list |input_method_ids| is sorted by popularity, hence 584 // input_method_ids[0] now contains the most popular keyboard layout 585 // for the given locale. 586 DCHECK_GE(input_method_ids.size(), 1U); 587 layout = input_method_ids[0]; 588 } 589 } 590 591 if (layout.empty()) 592 return; 593 594 std::vector<std::string> layouts; 595 base::SplitString(layout, ',', &layouts); 596 manager_->MigrateInputMethods(&layouts); 597 598 PrefService* prefs = g_browser_process->local_state(); 599 prefs->SetString(prefs::kHardwareKeyboardLayout, JoinString(layouts, ",")); 600 601 // This asks the file thread to save the prefs (i.e. doesn't block). 602 // The latest values of Local State reside in memory so we can safely 603 // get the value of kHardwareKeyboardLayout even if the data is not 604 // yet saved to disk. 605 prefs->CommitPendingWrite(); 606 607 manager_->util_.UpdateHardwareLayoutCache(); 608 609 EnableLoginLayouts(locale, layouts); 610 manager_->LoadNecessaryComponentExtensions(this); 611 } 612 613 void InputMethodManagerImpl::StateImpl::SetInputMethodLoginDefault() { 614 // Set up keyboards. For example, when |locale| is "en-US", enable US qwerty 615 // and US dvorak keyboard layouts. 616 if (g_browser_process && g_browser_process->local_state()) { 617 const std::string locale = g_browser_process->GetApplicationLocale(); 618 // If the preferred keyboard for the login screen has been saved, use it. 619 PrefService* prefs = g_browser_process->local_state(); 620 std::string initial_input_method_id = 621 prefs->GetString(chromeos::language_prefs::kPreferredKeyboardLayout); 622 std::vector<std::string> input_methods_to_be_enabled; 623 if (initial_input_method_id.empty()) { 624 // If kPreferredKeyboardLayout is not specified, use the hardware layout. 625 input_methods_to_be_enabled = 626 manager_->util_.GetHardwareLoginInputMethodIds(); 627 } else { 628 input_methods_to_be_enabled.push_back(initial_input_method_id); 629 } 630 EnableLoginLayouts(locale, input_methods_to_be_enabled); 631 manager_->LoadNecessaryComponentExtensions(this); 632 } 633 } 634 635 bool InputMethodManagerImpl::StateImpl::SwitchToNextInputMethod() { 636 // Sanity checks. 637 if (active_input_method_ids.empty()) { 638 DVLOG(1) << "active input method is empty"; 639 return false; 640 } 641 642 if (current_input_method.id().empty()) { 643 DVLOG(1) << "current_input_method is unknown"; 644 return false; 645 } 646 647 // Do not consume key event if there is only one input method is enabled. 648 // Ctrl+Space or Alt+Shift may be used by other application. 649 if (active_input_method_ids.size() == 1) 650 return false; 651 652 // Find the next input method and switch to it. 653 SwitchToNextInputMethodInternal(active_input_method_ids, 654 current_input_method.id()); 655 return true; 656 } 657 658 bool InputMethodManagerImpl::StateImpl::SwitchToPreviousInputMethod( 659 const ui::Accelerator& accelerator) { 660 // Sanity check. 661 if (active_input_method_ids.empty()) { 662 DVLOG(1) << "active input method is empty"; 663 return false; 664 } 665 666 // Do not consume key event if there is only one input method is enabled. 667 // Ctrl+Space or Alt+Shift may be used by other application. 668 if (active_input_method_ids.size() == 1) 669 return false; 670 671 if (accelerator.type() == ui::ET_KEY_RELEASED) 672 return true; 673 674 if (previous_input_method.id().empty() || 675 previous_input_method.id() == current_input_method.id()) { 676 return SwitchToNextInputMethod(); 677 } 678 679 std::vector<std::string>::const_iterator iter = 680 std::find(active_input_method_ids.begin(), 681 active_input_method_ids.end(), 682 previous_input_method.id()); 683 if (iter == active_input_method_ids.end()) { 684 // previous_input_method is not supported. 685 return SwitchToNextInputMethod(); 686 } 687 ChangeInputMethod(*iter, true); 688 689 return true; 690 } 691 692 bool InputMethodManagerImpl::StateImpl::SwitchInputMethod( 693 const ui::Accelerator& accelerator) { 694 // Sanity check. 695 if (active_input_method_ids.empty()) { 696 DVLOG(1) << "active input method is empty"; 697 return false; 698 } 699 700 // Get the list of input method ids for the |accelerator|. For example, get 701 // { "mozc-hangul", "xkb:kr:kr104:kor" } for ui::VKEY_DBE_SBCSCHAR. 702 std::vector<std::string> input_method_ids_to_switch; 703 switch (accelerator.key_code()) { 704 case ui::VKEY_CONVERT: // Henkan key on JP106 keyboard 705 input_method_ids_to_switch.push_back( 706 extension_ime_util::GetInputMethodIDByEngineID("nacl_mozc_jp")); 707 break; 708 case ui::VKEY_NONCONVERT: // Muhenkan key on JP106 keyboard 709 input_method_ids_to_switch.push_back( 710 extension_ime_util::GetInputMethodIDByEngineID("xkb:jp::jpn")); 711 break; 712 case ui::VKEY_DBE_SBCSCHAR: // ZenkakuHankaku key on JP106 keyboard 713 case ui::VKEY_DBE_DBCSCHAR: 714 input_method_ids_to_switch.push_back( 715 extension_ime_util::GetInputMethodIDByEngineID("nacl_mozc_jp")); 716 input_method_ids_to_switch.push_back( 717 extension_ime_util::GetInputMethodIDByEngineID("xkb:jp::jpn")); 718 break; 719 default: 720 NOTREACHED(); 721 break; 722 } 723 if (input_method_ids_to_switch.empty()) { 724 DVLOG(1) << "Unexpected VKEY: " << accelerator.key_code(); 725 return false; 726 } 727 728 // Obtain the intersection of input_method_ids_to_switch and 729 // active_input_method_ids. The order of IDs in active_input_method_ids is 730 // preserved. 731 std::vector<std::string> ids; 732 for (size_t i = 0; i < input_method_ids_to_switch.size(); ++i) { 733 const std::string& id = input_method_ids_to_switch[i]; 734 if (Contains(active_input_method_ids, id)) 735 ids.push_back(id); 736 } 737 if (ids.empty()) { 738 // No input method for the accelerator is active. For example, we should 739 // just ignore VKEY_HANGUL when mozc-hangul is not active. 740 return false; 741 } 742 743 SwitchToNextInputMethodInternal(ids, current_input_method.id()); 744 return true; // consume the accelerator. 745 } 746 747 void InputMethodManagerImpl::StateImpl::SwitchToNextInputMethodInternal( 748 const std::vector<std::string>& input_method_ids, 749 const std::string& current_input_methodid) { 750 std::vector<std::string>::const_iterator iter = std::find( 751 input_method_ids.begin(), input_method_ids.end(), current_input_methodid); 752 if (iter != input_method_ids.end()) 753 ++iter; 754 if (iter == input_method_ids.end()) 755 iter = input_method_ids.begin(); 756 ChangeInputMethod(*iter, true); 757 } 758 759 InputMethodDescriptor InputMethodManagerImpl::StateImpl::GetCurrentInputMethod() 760 const { 761 if (current_input_method.id().empty()) 762 return InputMethodUtil::GetFallbackInputMethodDescriptor(); 763 764 return current_input_method; 765 } 766 767 bool InputMethodManagerImpl::StateImpl::InputMethodIsActivated( 768 const std::string& input_method_id) const { 769 return Contains(active_input_method_ids, input_method_id); 770 } 771 772 // ------------------------ InputMethodManagerImpl 773 bool InputMethodManagerImpl::IsLoginKeyboard( 774 const std::string& layout) const { 775 return util_.IsLoginKeyboard(layout); 776 } 777 778 bool InputMethodManagerImpl::MigrateInputMethods( 779 std::vector<std::string>* input_method_ids) { 780 return util_.MigrateInputMethods(input_method_ids); 781 } 782 783 // Starts or stops the system input method framework as needed. 784 void InputMethodManagerImpl::ReconfigureIMFramework( 785 InputMethodManagerImpl::StateImpl* state) { 786 LoadNecessaryComponentExtensions(state); 787 788 // Initialize candidate window controller and widgets such as 789 // candidate window, infolist and mode indicator. Note, mode 790 // indicator is used by only keyboard layout input methods. 791 if (state_.get() == state) 792 MaybeInitializeCandidateWindowController(); 793 } 794 795 void InputMethodManagerImpl::SetState( 796 scoped_refptr<InputMethodManager::State> state) { 797 DCHECK(state.get()); 798 InputMethodManagerImpl::StateImpl* new_impl_state = 799 static_cast<InputMethodManagerImpl::StateImpl*>(state.get()); 800 const bool need_update_current_input_method = 801 (state_.get() 802 ? state_->current_input_method.id() != 803 new_impl_state->current_input_method.id() 804 : true); 805 state_ = new_impl_state; 806 807 if (state_.get() && state_->active_input_method_ids.size()) { 808 // Initialize candidate window controller and widgets such as 809 // candidate window, infolist and mode indicator. Note, mode 810 // indicator is used by only keyboard layout input methods. 811 MaybeInitializeCandidateWindowController(); 812 813 if (need_update_current_input_method) { 814 ChangeInputMethodInternal(state_->current_input_method, 815 false /* show_message */, 816 true /* notify_menu */); 817 } else { 818 // Update input method indicators (e.g. "US", "DV") in Chrome windows. 819 FOR_EACH_OBSERVER(InputMethodManager::Observer, 820 observers_, 821 InputMethodChanged(this, false /* show_message */)); 822 } 823 } 824 } 825 826 scoped_refptr<InputMethodManager::State> 827 InputMethodManagerImpl::GetActiveIMEState() { 828 return scoped_refptr<InputMethodManager::State>(state_.get()); 829 } 830 831 InputMethodManagerImpl::InputMethodManagerImpl( 832 scoped_ptr<InputMethodDelegate> delegate, 833 bool enable_extension_loading) 834 : delegate_(delegate.Pass()), 835 ui_session_(STATE_LOGIN_SCREEN), 836 state_(NULL), 837 util_(delegate_.get()), 838 component_extension_ime_manager_(new ComponentExtensionIMEManager()), 839 enable_extension_loading_(enable_extension_loading) { 840 if (base::SysInfo::IsRunningOnChromeOS()) 841 keyboard_.reset(ImeKeyboard::Create()); 842 else 843 keyboard_.reset(new FakeImeKeyboard()); 844 845 // Initializes the system IME list. 846 scoped_ptr<ComponentExtensionIMEManagerDelegate> comp_delegate( 847 new ComponentExtensionIMEManagerImpl()); 848 component_extension_ime_manager_->Initialize(comp_delegate.Pass()); 849 const InputMethodDescriptors& descriptors = 850 component_extension_ime_manager_->GetAllIMEAsInputMethodDescriptor(); 851 util_.ResetInputMethods(descriptors); 852 853 // Initializes the stat id map. 854 std::map<int, std::vector<std::string> > buckets; 855 for (InputMethodDescriptors::const_iterator it = descriptors.begin(); 856 it != descriptors.end(); ++it) { 857 char first_char; 858 int cat_id = static_cast<int>( 859 GetInputMethodCategory(it->id(), &first_char)); 860 int key = cat_id * 1000 + first_char; 861 buckets[key].push_back(it->id()); 862 } 863 for (std::map<int, std::vector<std::string>>::iterator i = 864 buckets.begin(); i != buckets.end(); ++i) { 865 std::sort(i->second.begin(), i->second.end()); 866 for (size_t j = 0; j < i->second.size() && j < 100; ++j) { 867 int key = i->first * 100 + j; 868 stat_id_map_[i->second[j]] = key; 869 } 870 } 871 } 872 873 InputMethodManagerImpl::~InputMethodManagerImpl() { 874 if (candidate_window_controller_.get()) 875 candidate_window_controller_->RemoveObserver(this); 876 } 877 878 void InputMethodManagerImpl::RecordInputMethodUsage( 879 std::string input_method_id) { 880 UMA_HISTOGRAM_ENUMERATION("InputMethod.Category", 881 GetInputMethodCategory(input_method_id), 882 INPUT_METHOD_CATEGORY_MAX); 883 UMA_HISTOGRAM_SPARSE_SLOWLY("InputMethod.ID", 884 stat_id_map_[input_method_id]); 885 } 886 887 void InputMethodManagerImpl::AddObserver( 888 InputMethodManager::Observer* observer) { 889 observers_.AddObserver(observer); 890 } 891 892 void InputMethodManagerImpl::AddCandidateWindowObserver( 893 InputMethodManager::CandidateWindowObserver* observer) { 894 candidate_window_observers_.AddObserver(observer); 895 } 896 897 void InputMethodManagerImpl::RemoveObserver( 898 InputMethodManager::Observer* observer) { 899 observers_.RemoveObserver(observer); 900 } 901 902 void InputMethodManagerImpl::RemoveCandidateWindowObserver( 903 InputMethodManager::CandidateWindowObserver* observer) { 904 candidate_window_observers_.RemoveObserver(observer); 905 } 906 907 InputMethodManager::UISessionState InputMethodManagerImpl::GetUISessionState() { 908 return ui_session_; 909 } 910 911 void InputMethodManagerImpl::SetUISessionState(UISessionState new_ui_session) { 912 ui_session_ = new_ui_session; 913 switch (ui_session_) { 914 case STATE_LOGIN_SCREEN: 915 break; 916 case STATE_BROWSER_SCREEN: 917 break; 918 case STATE_LOCK_SCREEN: 919 break; 920 case STATE_TERMINATING: { 921 if (candidate_window_controller_.get()) 922 candidate_window_controller_.reset(); 923 break; 924 } 925 } 926 } 927 928 scoped_ptr<InputMethodDescriptors> 929 InputMethodManagerImpl::GetSupportedInputMethods() const { 930 return scoped_ptr<InputMethodDescriptors>(new InputMethodDescriptors).Pass(); 931 } 932 933 const InputMethodDescriptor* InputMethodManagerImpl::LookupInputMethod( 934 const std::string& input_method_id, 935 InputMethodManagerImpl::StateImpl* state) { 936 DCHECK(state); 937 938 std::string input_method_id_to_switch = input_method_id; 939 940 // Sanity check 941 if (!state->InputMethodIsActivated(input_method_id)) { 942 scoped_ptr<InputMethodDescriptors> input_methods( 943 state->GetActiveInputMethods()); 944 DCHECK(!input_methods->empty()); 945 input_method_id_to_switch = input_methods->at(0).id(); 946 if (!input_method_id.empty()) { 947 DVLOG(1) << "Can't change the current input method to " 948 << input_method_id << " since the engine is not enabled. " 949 << "Switch to " << input_method_id_to_switch << " instead."; 950 } 951 } 952 953 const InputMethodDescriptor* descriptor = NULL; 954 if (extension_ime_util::IsExtensionIME(input_method_id_to_switch)) { 955 DCHECK(state->extra_input_methods.find(input_method_id_to_switch) != 956 state->extra_input_methods.end()); 957 descriptor = &(state->extra_input_methods[input_method_id_to_switch]); 958 } else { 959 descriptor = 960 util_.GetInputMethodDescriptorFromId(input_method_id_to_switch); 961 if (!descriptor) 962 LOG(ERROR) << "Unknown input method id: " << input_method_id_to_switch; 963 } 964 DCHECK(descriptor); 965 return descriptor; 966 } 967 968 void InputMethodManagerImpl::ChangeInputMethodInternal( 969 const InputMethodDescriptor& descriptor, 970 bool show_message, 971 bool notify_menu) { 972 // No need to switch input method when terminating. 973 if (ui_session_ == STATE_TERMINATING) 974 return; 975 976 if (candidate_window_controller_.get()) 977 candidate_window_controller_->Hide(); 978 979 if (notify_menu) { 980 // Clear property list. Property list would be updated by 981 // extension IMEs via InputMethodEngine::(Set|Update)MenuItems. 982 // If the current input method is a keyboard layout, empty 983 // properties are sufficient. 984 const ash::ime::InputMethodMenuItemList empty_menu_item_list; 985 ash::ime::InputMethodMenuManager* input_method_menu_manager = 986 ash::ime::InputMethodMenuManager::GetInstance(); 987 input_method_menu_manager->SetCurrentInputMethodMenuItemList( 988 empty_menu_item_list); 989 } 990 991 // Disable the current engine handler. 992 IMEEngineHandlerInterface* engine = 993 IMEBridge::Get()->GetCurrentEngineHandler(); 994 if (engine) 995 engine->Disable(); 996 997 // Configure the next engine handler. 998 // This must be after |current_input_method| has been set to new input 999 // method, because engine's Enable() method needs to access it. 1000 const std::string& extension_id = 1001 extension_ime_util::GetExtensionIDFromInputMethodID(descriptor.id()); 1002 const std::string& component_id = 1003 extension_ime_util::GetComponentIDByInputMethodID(descriptor.id()); 1004 engine = engine_map_[extension_id]; 1005 1006 IMEBridge::Get()->SetCurrentEngineHandler(engine); 1007 1008 if (engine) { 1009 engine->Enable(component_id); 1010 } else { 1011 // If no engine to enable, cancel the virtual keyboard url override so that 1012 // it can use the fallback system virtual keyboard UI. 1013 keyboard::SetOverrideContentUrl(GURL()); 1014 keyboard::KeyboardController* keyboard_controller = 1015 keyboard::KeyboardController::GetInstance(); 1016 if (keyboard_controller) 1017 keyboard_controller->Reload(); 1018 } 1019 1020 // Change the keyboard layout to a preferred layout for the input method. 1021 if (!keyboard_->SetCurrentKeyboardLayoutByName( 1022 descriptor.GetPreferredKeyboardLayout())) { 1023 LOG(ERROR) << "Failed to change keyboard layout to " 1024 << descriptor.GetPreferredKeyboardLayout(); 1025 } 1026 1027 // Update input method indicators (e.g. "US", "DV") in Chrome windows. 1028 FOR_EACH_OBSERVER(InputMethodManager::Observer, 1029 observers_, 1030 InputMethodChanged(this, show_message)); 1031 } 1032 1033 void InputMethodManagerImpl::LoadNecessaryComponentExtensions( 1034 InputMethodManagerImpl::StateImpl* state) { 1035 // Load component extensions but also update |active_input_method_ids| as 1036 // some component extension IMEs may have been removed from the Chrome OS 1037 // image. If specified component extension IME no longer exists, falling back 1038 // to an existing IME. 1039 DCHECK(state); 1040 std::vector<std::string> unfiltered_input_method_ids; 1041 unfiltered_input_method_ids.swap(state->active_input_method_ids); 1042 for (size_t i = 0; i < unfiltered_input_method_ids.size(); ++i) { 1043 if (!extension_ime_util::IsComponentExtensionIME( 1044 unfiltered_input_method_ids[i])) { 1045 // Legacy IMEs or xkb layouts are alwayes active. 1046 state->active_input_method_ids.push_back(unfiltered_input_method_ids[i]); 1047 } else if (component_extension_ime_manager_->IsWhitelisted( 1048 unfiltered_input_method_ids[i])) { 1049 if (enable_extension_loading_) { 1050 component_extension_ime_manager_->LoadComponentExtensionIME( 1051 state->profile, unfiltered_input_method_ids[i]); 1052 } 1053 1054 state->active_input_method_ids.push_back(unfiltered_input_method_ids[i]); 1055 } 1056 } 1057 } 1058 1059 void InputMethodManagerImpl::ActivateInputMethodMenuItem( 1060 const std::string& key) { 1061 DCHECK(!key.empty()); 1062 1063 if (ash::ime::InputMethodMenuManager::GetInstance()-> 1064 HasInputMethodMenuItemForKey(key)) { 1065 IMEEngineHandlerInterface* engine = 1066 IMEBridge::Get()->GetCurrentEngineHandler(); 1067 if (engine) 1068 engine->PropertyActivate(key); 1069 return; 1070 } 1071 1072 DVLOG(1) << "ActivateInputMethodMenuItem: unknown key: " << key; 1073 } 1074 1075 bool InputMethodManagerImpl::IsISOLevel5ShiftUsedByCurrentInputMethod() const { 1076 return keyboard_->IsISOLevel5ShiftAvailable(); 1077 } 1078 1079 bool InputMethodManagerImpl::IsAltGrUsedByCurrentInputMethod() const { 1080 return keyboard_->IsAltGrAvailable(); 1081 } 1082 1083 ImeKeyboard* InputMethodManagerImpl::GetImeKeyboard() { 1084 return keyboard_.get(); 1085 } 1086 1087 InputMethodUtil* InputMethodManagerImpl::GetInputMethodUtil() { 1088 return &util_; 1089 } 1090 1091 ComponentExtensionIMEManager* 1092 InputMethodManagerImpl::GetComponentExtensionIMEManager() { 1093 return component_extension_ime_manager_.get(); 1094 } 1095 1096 scoped_refptr<InputMethodManager::State> InputMethodManagerImpl::CreateNewState( 1097 Profile* profile) { 1098 return scoped_refptr<InputMethodManager::State>(new StateImpl(this, profile)); 1099 } 1100 1101 void InputMethodManagerImpl::SetCandidateWindowControllerForTesting( 1102 CandidateWindowController* candidate_window_controller) { 1103 candidate_window_controller_.reset(candidate_window_controller); 1104 candidate_window_controller_->AddObserver(this); 1105 } 1106 1107 void InputMethodManagerImpl::SetImeKeyboardForTesting(ImeKeyboard* keyboard) { 1108 keyboard_.reset(keyboard); 1109 } 1110 1111 void InputMethodManagerImpl::InitializeComponentExtensionForTesting( 1112 scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate) { 1113 component_extension_ime_manager_->Initialize(delegate.Pass()); 1114 util_.ResetInputMethods( 1115 component_extension_ime_manager_->GetAllIMEAsInputMethodDescriptor()); 1116 } 1117 1118 void InputMethodManagerImpl::CandidateClicked(int index) { 1119 IMEEngineHandlerInterface* engine = 1120 IMEBridge::Get()->GetCurrentEngineHandler(); 1121 if (engine) 1122 engine->CandidateClicked(index); 1123 } 1124 1125 void InputMethodManagerImpl::CandidateWindowOpened() { 1126 FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver, 1127 candidate_window_observers_, 1128 CandidateWindowOpened(this)); 1129 } 1130 1131 void InputMethodManagerImpl::CandidateWindowClosed() { 1132 FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver, 1133 candidate_window_observers_, 1134 CandidateWindowClosed(this)); 1135 } 1136 1137 void InputMethodManagerImpl::MaybeInitializeCandidateWindowController() { 1138 if (candidate_window_controller_.get()) 1139 return; 1140 1141 candidate_window_controller_.reset( 1142 CandidateWindowController::CreateCandidateWindowController()); 1143 candidate_window_controller_->AddObserver(this); 1144 } 1145 1146 } // namespace input_method 1147 } // namespace chromeos 1148