chromium/ash/ime/ime_controller_impl.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/ime/ime_controller_impl.h"

#include <utility>

#include "ash/ime/ime_mode_indicator_view.h"
#include "ash/ime/ime_switch_type.h"
#include "ash/ime/mode_indicator_observer.h"
#include "ash/shell.h"
#include "ash/system/tray/system_tray_notifier.h"
#include "base/containers/contains.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "base/ranges/algorithm.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/ime/ash/extension_ime_util.h"
#include "ui/display/manager/display_manager.h"

namespace ash {

namespace {

// The result of pressing VKEY_MODECHANGE (for metrics).
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class ModeChangeKeyAction {
  kShowIndicator = 0,
  kSwitchIme = 1,
  kMaxValue = kSwitchIme
};

// The ID for the Accessibility Common IME (used for Dictation).
const char* kAccessibilityCommonIMEId =
    "_ext_ime_egfdjlfmgnehecnclamagfafdccgfndpdictation";

}  // namespace

ImeControllerImpl::ImeControllerImpl()
    : mode_indicator_observer_(std::make_unique<ModeIndicatorObserver>()) {}

ImeControllerImpl::~ImeControllerImpl() {
  SetClient(nullptr);
}

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

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

const std::vector<ImeInfo>& ImeControllerImpl::GetVisibleImes() const {
  return visible_imes_;
}

bool ImeControllerImpl::IsCurrentImeVisible() const {
  return current_ime_.id != kAccessibilityCommonIMEId;
}

void ImeControllerImpl::SetClient(ImeControllerClient* client) {
  client_ = client;
}

bool ImeControllerImpl::CanSwitchIme() const {
  // Cannot switch unless there is an active IME.
  if (current_ime_.id.empty())
    return false;

  // Do not consume key event if there is only one input method is enabled.
  // Ctrl+Space or Alt+Shift may be used by other application.
  return GetVisibleImes().size() > 1;
}

void ImeControllerImpl::SwitchToNextIme() {
  if (client_)
    client_->SwitchToNextIme();
}

void ImeControllerImpl::SwitchToLastUsedIme() {
  if (client_)
    client_->SwitchToLastUsedIme();
}

void ImeControllerImpl::SwitchImeById(const std::string& ime_id,
                                      bool show_message) {
  if (client_)
    client_->SwitchImeById(ime_id, show_message);
}

void ImeControllerImpl::ActivateImeMenuItem(const std::string& key) {
  if (client_)
    client_->ActivateImeMenuItem(key);
}

bool ImeControllerImpl::CanSwitchImeWithAccelerator(
    const ui::Accelerator& accelerator) const {
  // If none of the input methods associated with |accelerator| are active, we
  // should ignore the accelerator.
  std::vector<std::string> candidate_ids =
      GetCandidateImesForAccelerator(accelerator);
  return !candidate_ids.empty();
}

void ImeControllerImpl::SwitchImeWithAccelerator(
    const ui::Accelerator& accelerator) {
  if (!client_)
    return;

  std::vector<std::string> candidate_ids =
      GetCandidateImesForAccelerator(accelerator);
  if (candidate_ids.empty())
    return;
  auto it = base::ranges::find(candidate_ids, current_ime_.id);
  if (it != candidate_ids.end())
    ++it;
  if (it == candidate_ids.end())
    it = candidate_ids.begin();
  client_->SwitchImeById(*it, true /* show_message */);
}

// ImeControllerImpl:
void ImeControllerImpl::RefreshIme(const std::string& current_ime_id,
                                   std::vector<ImeInfo> available_imes,
                                   std::vector<ImeMenuItem> menu_items) {
  if (current_ime_id.empty())
    current_ime_ = ImeInfo();

  available_imes_.clear();
  available_imes_.reserve(available_imes.size());
  visible_imes_.clear();
  visible_imes_.reserve(visible_imes_.size());
  for (const auto& ime : available_imes) {
    if (ime.id.empty()) {
      DLOG(ERROR) << "Received IME with invalid ID.";
      continue;
    }
    available_imes_.push_back(ime);
    if (ime.id != kAccessibilityCommonIMEId) {
      visible_imes_.push_back(ime);
    }
    if (ime.id == current_ime_id)
      current_ime_ = ime;
  }

  // Either there is no current IME or we found a valid one in the list of
  // available IMEs.
  DCHECK(current_ime_id.empty() || !current_ime_.id.empty());

  current_ime_menu_items_.clear();
  current_ime_menu_items_.reserve(menu_items.size());
  for (const auto& item : menu_items)
    current_ime_menu_items_.push_back(item);

  Shell::Get()->system_tray_notifier()->NotifyRefreshIME();
}

void ImeControllerImpl::SetImesManagedByPolicy(bool managed) {
  managed_by_policy_ = managed;
  Shell::Get()->system_tray_notifier()->NotifyRefreshIME();
}

void ImeControllerImpl::ShowImeMenuOnShelf(bool show) {
  is_menu_active_ = show;
  Shell::Get()->system_tray_notifier()->NotifyRefreshIMEMenu(show);
}

void ImeControllerImpl::UpdateCapsLockState(bool caps_enabled) {
  is_caps_lock_enabled_ = caps_enabled;

  for (ImeController::Observer& observer : observers_) {
    observer.OnCapsLockChanged(caps_enabled);
  }
}

void ImeControllerImpl::OnKeyboardLayoutNameChanged(
    const std::string& layout_name) {
  keyboard_layout_name_ = layout_name;

  for (ImeController::Observer& observer : observers_) {
    observer.OnKeyboardLayoutNameChanged(layout_name);
  }
}

void ImeControllerImpl::OnKeyboardEnabledChanged(bool is_enabled) {
  if (!is_enabled) {
    OverrideKeyboardKeyset(input_method::ImeKeyset::kNone);
  }
}

void ImeControllerImpl::SetExtraInputOptionsEnabledState(
    bool is_extra_input_options_enabled,
    bool is_emoji_enabled,
    bool is_handwriting_enabled,
    bool is_voice_enabled) {
  is_extra_input_options_enabled_ = is_extra_input_options_enabled;
  is_emoji_enabled_ = is_emoji_enabled;
  is_handwriting_enabled_ = is_handwriting_enabled;
  is_voice_enabled_ = is_voice_enabled;
}

void ImeControllerImpl::ShowModeIndicator(
    const gfx::Rect& anchor_bounds,
    const std::u16string& ime_short_name) {
  ImeModeIndicatorView* mi_view =
      new ImeModeIndicatorView(anchor_bounds, ime_short_name);
  views::BubbleDialogDelegateView::CreateBubble(mi_view);
  mode_indicator_observer_->AddModeIndicatorWidget(mi_view->GetWidget());
  mi_view->ShowAndFadeOut();
}

void ImeControllerImpl::SetCapsLockEnabled(bool caps_enabled) {
  is_caps_lock_enabled_ = caps_enabled;

  if (client_)
    client_->SetCapsLockEnabled(caps_enabled);
}

void ImeControllerImpl::OverrideKeyboardKeyset(input_method::ImeKeyset keyset) {
  OverrideKeyboardKeyset(keyset, base::DoNothing());
}

void ImeControllerImpl::OverrideKeyboardKeyset(
    input_method::ImeKeyset keyset,
    ImeControllerClient::OverrideKeyboardKeysetCallback callback) {
  if (client_)
    client_->OverrideKeyboardKeyset(keyset, std::move(callback));
}

bool ImeControllerImpl::IsCapsLockEnabled() const {
  return is_caps_lock_enabled_;
}

std::vector<std::string> ImeControllerImpl::GetCandidateImesForAccelerator(
    const ui::Accelerator& accelerator) const {
  std::vector<std::string> candidate_ids;

  using extension_ime_util::GetInputMethodIDByEngineID;
  std::vector<std::string> input_method_ids_to_switch;
  switch (accelerator.key_code()) {
    case ui::VKEY_CONVERT:  // Henkan key on JP106 keyboard
      input_method_ids_to_switch.push_back(
          GetInputMethodIDByEngineID("nacl_mozc_jp"));
      break;
    case ui::VKEY_NONCONVERT:  // Muhenkan key on JP106 keyboard
      input_method_ids_to_switch.push_back(
          GetInputMethodIDByEngineID("xkb:jp::jpn"));
      break;
    case ui::VKEY_DBE_SBCSCHAR:  // ZenkakuHankaku key on JP106 keyboard
    case ui::VKEY_DBE_DBCSCHAR:
      input_method_ids_to_switch.push_back(
          GetInputMethodIDByEngineID("nacl_mozc_jp"));
      input_method_ids_to_switch.push_back(
          GetInputMethodIDByEngineID("xkb:jp::jpn"));
      break;
    default:
      break;
  }
  if (input_method_ids_to_switch.empty()) {
    DVLOG(1) << "Unexpected VKEY: " << accelerator.key_code();
    return std::vector<std::string>();
  }

  // Obtain the intersection of input_method_ids_to_switch and available_imes_.
  for (const ImeInfo& ime : available_imes_) {
    if (base::Contains(input_method_ids_to_switch, ime.id))
      candidate_ids.push_back(ime.id);
  }
  return candidate_ids;
}

}  // namespace ash