chromium/ash/components/arc/ime/arc_ime_service.cc

// Copyright 2016 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/components/arc/ime/arc_ime_service.h"

#include <tuple>
#include <utility>

#include "ash/components/arc/arc_browser_context_keyed_service_factory_base.h"
#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/ime/arc_ime_bridge_impl.h"
#include "ash/components/arc/ime/arc_ime_util.h"
#include "ash/components/arc/ime/key_event_result_receiver.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/public/cpp/app_types_util.h"
#include "ash/public/cpp/external_arc/message_center/arc_notification_content_view.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/exo/wm_helper.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/ime/constants.h"
#include "ui/base/ime/ime_key_event_dispatcher.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/text_input_flags.h"
#include "ui/display/screen.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/range/range.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/non_client_view.h"
#include "ui/wm/core/ime_util_chromeos.h"

namespace arc {

namespace {

std::optional<double> g_override_default_device_scale_factor;
std::optional<gfx::Point> g_override_display_origin;

// Return true when a rich text editing is available on a text field with the
// given type.
bool IsTextInputActive(ui::TextInputType type) {
  return type != ui::TEXT_INPUT_TYPE_NONE && type != ui::TEXT_INPUT_TYPE_NULL;
}

// Return true if the given key event generats a visible character.
bool IsCharacterKeyEvent(const ui::KeyEvent* event) {
  return !IsControlChar(event) && !ui::IsSystemKeyModifier(event->flags());
}

// Return true if the given key event is used for language switching by IME.
// Please refer to `ash::InputMethodAsh::DispatchKeyEvent` for details.
bool IsLanguageInputKey(const ui::KeyEvent* event) {
  switch (event->key_code()) {
    case ui::VKEY_CONVERT:
    case ui::VKEY_NONCONVERT:
    case ui::VKEY_DBE_SBCSCHAR:
    case ui::VKEY_DBE_DBCSCHAR:
      return true;
    default:
      return false;
  }
}

int CursorBehaviorToCursorPosition(
    ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
  switch (cursor_behavior) {
    case ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText:
      return 1;
    case ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorBeforeText:
      return 0;
  }
}

class ArcWindowDelegateImpl : public ArcImeService::ArcWindowDelegate {
 public:
  explicit ArcWindowDelegateImpl(ArcImeService* ime_service)
      : ime_service_(ime_service) {}

  ArcWindowDelegateImpl(const ArcWindowDelegateImpl&) = delete;
  ArcWindowDelegateImpl& operator=(const ArcWindowDelegateImpl&) = delete;

  ~ArcWindowDelegateImpl() override = default;

  bool IsInArcAppWindow(const aura::Window* window) const override {
    // WMHelper is not created in browser_tests.
    if (!exo::WMHelper::HasInstance())
      return false;
    for (; window; window = window->parent()) {
      if (ash::IsArcWindow(window))
        return true;

      // TODO(crbug.com/1168334): Find a correct way to detect the ARC++
      // notifications. It should be okay for now because only the ARC++ windows
      // have kSkipImeProcessing.
      if (window->GetProperty(aura::client::kSkipImeProcessing))
        return true;
    }
    return false;
  }

  void RegisterFocusObserver() override {
    // WMHelper is not craeted in browser_tests.
    if (!exo::WMHelper::HasInstance())
      return;
    exo::WMHelper::GetInstance()->AddFocusObserver(ime_service_);
  }

  void UnregisterFocusObserver() override {
    // If WMHelper is already destroyed, do nothing.
    // TODO(crbug.com/40531599): Fix shutdown order.
    if (!exo::WMHelper::HasInstance())
      return;
    exo::WMHelper::GetInstance()->RemoveFocusObserver(ime_service_);
  }

  ui::InputMethod* GetInputMethodForWindow(
      aura::Window* window) const override {
    if (!window || !window->GetHost())
      return nullptr;
    return window->GetHost()->GetInputMethod();
  }

 private:
  const raw_ptr<ArcImeService> ime_service_;
};

// Singleton factory for ArcImeService.
class ArcImeServiceFactory
    : public internal::ArcBrowserContextKeyedServiceFactoryBase<
          ArcImeService,
          ArcImeServiceFactory> {
 public:
  // Factory name used by ArcBrowserContextKeyedServiceFactoryBase.
  static constexpr const char* kName = "ArcImeServiceFactory";

  static ArcImeServiceFactory* GetInstance() {
    return base::Singleton<ArcImeServiceFactory>::get();
  }

 private:
  friend base::DefaultSingletonTraits<ArcImeServiceFactory>;
  ArcImeServiceFactory() = default;
  ~ArcImeServiceFactory() override = default;
};

}  // anonymous namespace

////////////////////////////////////////////////////////////////////////////////
// ArcImeService main implementation:

// static
ArcImeService* ArcImeService::GetForBrowserContext(
    content::BrowserContext* context) {
  return ArcImeServiceFactory::GetForBrowserContext(context);
}

ArcImeService::ArcImeService(content::BrowserContext* context,
                             ArcBridgeService* bridge_service)
    : ArcImeService(context,
                    bridge_service,
                    std::make_unique<ArcWindowDelegateImpl>(this)) {}

ArcImeService::ArcImeService(content::BrowserContext* context,
                             ArcBridgeService* bridge_service,
                             std::unique_ptr<ArcWindowDelegate> delegate)
    : ime_bridge_(new ArcImeBridgeImpl(this, bridge_service)),
      arc_window_delegate_(std::move(delegate)),
      ime_type_(ui::TEXT_INPUT_TYPE_NONE),
      ime_flags_(ui::TEXT_INPUT_FLAG_NONE),
      is_personalized_learning_allowed_(false),
      has_composition_text_(false),
      receiver_(std::make_unique<KeyEventResultReceiver>()) {
  if (aura::Env::HasInstance())
    aura::Env::GetInstance()->AddObserver(this);
  arc_window_delegate_->RegisterFocusObserver();
}

ArcImeService::~ArcImeService() {
  ui::InputMethod* const input_method = GetInputMethod();
  if (input_method)
    input_method->DetachTextInputClient(this);

  if (focused_arc_window_)
    focused_arc_window_->RemoveObserver(this);
  arc_window_delegate_->UnregisterFocusObserver();
  if (aura::Env::HasInstance())
    aura::Env::GetInstance()->RemoveObserver(this);

  // KeyboardController is destroyed before ArcImeService (except in tests),
  // so check whether there is a KeyboardController first before removing |this|
  // from KeyboardController observers.
  if (keyboard::KeyboardUIController::HasInstance()) {
    auto* keyboard_controller = keyboard::KeyboardUIController::Get();
    if (keyboard_controller->HasObserver(this))
      keyboard_controller->RemoveObserver(this);
  }
}

void ArcImeService::SetImeBridgeForTesting(
    std::unique_ptr<ArcImeBridge> test_ime_bridge) {
  ime_bridge_ = std::move(test_ime_bridge);
}

ui::InputMethod* ArcImeService::GetInputMethod() {
  return arc_window_delegate_->GetInputMethodForWindow(focused_arc_window_);
}

void ArcImeService::ReattachInputMethod(aura::Window* old_window,
                                        aura::Window* new_window) {
  ui::InputMethod* const old_ime =
      arc_window_delegate_->GetInputMethodForWindow(old_window);
  ui::InputMethod* const new_ime =
      arc_window_delegate_->GetInputMethodForWindow(new_window);

  if (old_ime != new_ime) {
    if (old_ime)
      old_ime->DetachTextInputClient(this);
    if (new_ime)
      new_ime->SetFocusedTextInputClient(this);
  }
}

////////////////////////////////////////////////////////////////////////////////
// Overridden from aura::EnvObserver:

void ArcImeService::OnWindowInitialized(aura::Window* new_window) {
  if (keyboard::KeyboardUIController::HasInstance()) {
    auto* keyboard_controller = keyboard::KeyboardUIController::Get();
    if (keyboard_controller->IsEnabled() &&
        !keyboard_controller->HasObserver(this)) {
      keyboard_controller->AddObserver(this);
    }
  }
}

////////////////////////////////////////////////////////////////////////////////
// Overridden from aura::WindowObserver:

void ArcImeService::OnWindowDestroying(aura::Window* window) {
  // This shouldn't be reached on production, since the window lost the focus
  // and called OnWindowFocused() before destroying.
  // But we handle this case for testing.
  if (window == focused_arc_window_)
    OnWindowFocused(nullptr, focused_arc_window_);
}

void ArcImeService::OnWindowRemovingFromRootWindow(aura::Window* window,
                                                   aura::Window* new_root) {
  // IMEs are associated with root windows, hence we may need to detach/attach.
  if (window == focused_arc_window_)
    ReattachInputMethod(focused_arc_window_, new_root);
}

void ArcImeService::OnWindowRemoved(aura::Window* removed_window) {
  // |this| can lose the IME focus because |focused_arc_window_| may have
  // children other than ExoSurface e.g. WebContentsViewAura for CustomTabs.
  // Restore the IME focus when such a window is removed.
  ReattachInputMethod(nullptr, focused_arc_window_);
}

////////////////////////////////////////////////////////////////////////////////
// Overridden from exo::WMHelper::FocusChangeObserver:

void ArcImeService::OnWindowFocused(aura::Window* gained_focus,
                                    aura::Window* lost_focus) {
  if (lost_focus == gained_focus)
    return;

  const bool detach = (lost_focus && focused_arc_window_ == lost_focus);
  const bool attach = arc_window_delegate_->IsInArcAppWindow(gained_focus);

  if (detach) {
    // The focused window and the toplevel window are different in production,
    // but in tests they can be the same, so avoid adding the observer twice.
    if (focused_arc_window_ != focused_arc_window_->GetToplevelWindow())
      focused_arc_window_->GetToplevelWindow()->RemoveObserver(this);
    focused_arc_window_->RemoveObserver(this);
    focused_arc_window_ = nullptr;
  }
  if (attach) {
    DCHECK_EQ(nullptr, focused_arc_window_);
    focused_arc_window_ = gained_focus;
    focused_arc_window_->AddObserver(this);
    // The focused window and the toplevel window are different in production,
    // but in tests they can be the same, so avoid adding the observer twice.
    if (focused_arc_window_ != focused_arc_window_->GetToplevelWindow())
      focused_arc_window_->GetToplevelWindow()->AddObserver(this);
  }

  ReattachInputMethod(detach ? lost_focus : nullptr, focused_arc_window_);
}

////////////////////////////////////////////////////////////////////////////////
// Overridden from arc::ArcImeBridge::Delegate

void ArcImeService::OnTextInputTypeChanged(
    ui::TextInputType type,
    bool is_personalized_learning_allowed,
    int flags) {
  if (ime_type_ == type &&
      is_personalized_learning_allowed_ == is_personalized_learning_allowed &&
      ime_flags_ == flags) {
    return;
  }
  ime_type_ = type;
  is_personalized_learning_allowed_ = is_personalized_learning_allowed;
  ime_flags_ = flags;

  if (!ShouldSendUpdateToInputMethod())
    return;

  ui::InputMethod* const input_method = GetInputMethod();
  if (input_method)
    input_method->OnTextInputTypeChanged(this);

  // Call HideKeyboard() here. On a text field on an ARC++ app, just having
  // non-null text input type doesn't mean the virtual keyboard is necessary. If
  // the virtual keyboard is really needed, ShowVirtualKeyboardIfEnabled will be
  // called later.
  if (keyboard::KeyboardUIController::HasInstance()) {
    auto* keyboard_controller = keyboard::KeyboardUIController::Get();
    if (keyboard_controller->IsEnabled())
      keyboard_controller->HideKeyboardImplicitlyBySystem();
  }
}

void ArcImeService::OnCursorRectChanged(
    const gfx::Rect& rect,
    mojom::CursorCoordinateSpace coordinate_space) {
  if (!ShouldSendUpdateToInputMethod())
    return;

  InvalidateSurroundingTextAndSelectionRange();
  if (!UpdateCursorRect(rect, coordinate_space))
    return;

  ui::InputMethod* const input_method = GetInputMethod();
  if (input_method)
    input_method->OnCaretBoundsChanged(this);
}

void ArcImeService::OnCancelComposition() {
  if (!ShouldSendUpdateToInputMethod())
    return;

  InvalidateSurroundingTextAndSelectionRange();
  ui::InputMethod* const input_method = GetInputMethod();
  if (input_method)
    input_method->CancelComposition(this);
}

void ArcImeService::ShowVirtualKeyboardIfEnabled() {
  if (!ShouldSendUpdateToInputMethod())
    return;

  ui::InputMethod* const input_method = GetInputMethod();
  if (input_method && input_method->GetTextInputClient() == this) {
    input_method->SetVirtualKeyboardVisibilityIfEnabled(true);
  }
}

void ArcImeService::OnCursorRectChangedWithSurroundingText(
    const gfx::Rect& rect,
    const gfx::Range& text_range,
    const std::u16string& text_in_range,
    const gfx::Range& selection_range,
    mojom::CursorCoordinateSpace coordinate_space) {
  if (!ShouldSendUpdateToInputMethod())
    return;

  if (!UpdateCursorRect(rect, coordinate_space) && text_range_ == text_range &&
      text_in_range_ == text_in_range && selection_range_ == selection_range) {
    return;
  }

  text_range_ = text_range;
  text_in_range_ = text_in_range;
  selection_range_ = selection_range;

  ui::InputMethod* const input_method = GetInputMethod();
  if (input_method)
    input_method->OnCaretBoundsChanged(this);
}

void ArcImeService::SendKeyEvent(std::unique_ptr<ui::KeyEvent> key_event,
                                 KeyEventDoneCallback callback) {
  ui::InputMethod* const input_method = GetInputMethod();
  receiver_->SetCallback(std::move(callback), key_event.get());

  if (input_method)
    std::ignore = input_method->DispatchKeyEvent(key_event.get());
}

////////////////////////////////////////////////////////////////////////////////
// Overridden from ash::KeyboardControllerObserver
void ArcImeService::OnKeyboardAppearanceChanged(
    const ash::KeyboardStateDescriptor& state) {
  if (state.is_temporary) {
    return;
  }

  gfx::Rect new_bounds = state.occluded_bounds_in_screen;
  // Multiply by the scale factor. To convert from Chrome DIP to Android pixels.
  gfx::Rect bounds_in_px =
      gfx::ScaleToEnclosingRect(new_bounds, GetDeviceScaleFactorForKeyboard());

  ime_bridge_->SendOnKeyboardAppearanceChanging(bounds_in_px, state.is_visible);
}

////////////////////////////////////////////////////////////////////////////////
// Overridden from ui::TextInputClient:

base::WeakPtr<ui::TextInputClient> ArcImeService::AsWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

void ArcImeService::SetCompositionText(const ui::CompositionText& composition) {
  InvalidateSurroundingTextAndSelectionRange();
  has_composition_text_ = !composition.text.empty();
  ime_bridge_->SendSetCompositionText(composition);
}

size_t ArcImeService::ConfirmCompositionText(bool keep_selection) {
  if (!keep_selection) {
    InvalidateSurroundingTextAndSelectionRange();
  }
  has_composition_text_ = false;
  // Note: SendConfirmCompositonText() will commit the text and
  // keep the selection unchanged
  ime_bridge_->SendConfirmCompositionText();
  return std::numeric_limits<size_t>::max();
}

void ArcImeService::ClearCompositionText() {
  InvalidateSurroundingTextAndSelectionRange();
  if (has_composition_text_) {
    has_composition_text_ = false;
    ime_bridge_->SendInsertText(std::u16string(), /*new_cursor_position=*/1);
  }
}

void ArcImeService::InsertText(const std::u16string& text,
                               InsertTextCursorBehavior cursor_behavior) {
  InvalidateSurroundingTextAndSelectionRange();
  has_composition_text_ = false;
  ime_bridge_->SendInsertText(text,
                              CursorBehaviorToCursorPosition(cursor_behavior));
}

void ArcImeService::InsertChar(const ui::KeyEvent& event) {
  // According to the document in text_input_client.h, InsertChar() is called
  // even when the text editing is not available. We ignore such events, since
  // for ARC we are only interested in the event as a method of text input.
  if (!IsTextInputActive(ime_type_))
    return;

  InvalidateSurroundingTextAndSelectionRange();

  if (IsCharacterKeyEvent(&event)) {
    has_composition_text_ = false;
    ime_bridge_->SendInsertText(std::u16string(1, event.GetCharacter()),
                                /*new_cursor_position=*/1);
  }
}

ui::TextInputType ArcImeService::GetTextInputType() const {
  return ime_type_;
}

gfx::Rect ArcImeService::GetCaretBounds() const {
  return cursor_rect_;
}

gfx::Rect ArcImeService::GetSelectionBoundingBox() const {
  NOTIMPLEMENTED_LOG_ONCE();
  return gfx::Rect();
}

bool ArcImeService::GetTextRange(gfx::Range* range) const {
  if (!text_range_.IsValid())
    return false;
  *range = text_range_;
  return true;
}

bool ArcImeService::GetEditableSelectionRange(gfx::Range* range) const {
  if (!selection_range_.IsValid())
    return false;
  *range = selection_range_;
  return true;
}

bool ArcImeService::GetTextFromRange(const gfx::Range& range,
                                     std::u16string* text) const {
  // It's supposed that this method is called only from
  // InputMethod::OnCaretBoundsChanged(). In that method, the range obtained
  // from GetTextRange() is used as the argument of this method. To prevent an
  // unexpected usage, the check, |range != text_range_|, is added.
  if (!text_range_.IsValid() || range != text_range_)
    return false;
  *text = text_in_range_;
  return true;
}

void ArcImeService::EnsureCaretNotInRect(const gfx::Rect& rect_in_screen) {
  if (focused_arc_window_ == nullptr)
    return;
  aura::Window* top_level_window = focused_arc_window_->GetToplevelWindow();
  // If the window is not a notification, the window move is handled by
  // Android.
  if (top_level_window->GetType() != aura::client::WINDOW_TYPE_POPUP)
    return;
  wm::EnsureWindowNotInRect(top_level_window, rect_in_screen);
}

ui::TextInputMode ArcImeService::GetTextInputMode() const {
  return ui::TEXT_INPUT_MODE_DEFAULT;
}

base::i18n::TextDirection ArcImeService::GetTextDirection() const {
  return base::i18n::UNKNOWN_DIRECTION;
}

void ArcImeService::ExtendSelectionAndDelete(size_t before, size_t after) {
  InvalidateSurroundingTextAndSelectionRange();
  ime_bridge_->SendExtendSelectionAndDelete(before, after);
}

int ArcImeService::GetTextInputFlags() const {
  return ime_flags_;
}

bool ArcImeService::CanComposeInline() const {
  return true;
}

bool ArcImeService::GetCompositionCharacterBounds(size_t index,
                                                  gfx::Rect* rect) const {
  return false;
}

bool ArcImeService::HasCompositionText() const {
  return has_composition_text_;
}

ui::TextInputClient::FocusReason ArcImeService::GetFocusReason() const {
  // TODO(https://crbug.com/824604): Determine how the current input client got
  // focused.
  NOTIMPLEMENTED_LOG_ONCE();
  return ui::TextInputClient::FOCUS_REASON_OTHER;
}

bool ArcImeService::GetCompositionTextRange(gfx::Range* range) const {
  return false;
}

bool ArcImeService::SetEditableSelectionRange(const gfx::Range& range) {
  selection_range_ = range;
  ime_bridge_->SendSelectionRange(selection_range_);
  return true;
}

bool ArcImeService::ChangeTextDirectionAndLayoutAlignment(
    base::i18n::TextDirection direction) {
  return false;
}

bool ArcImeService::IsTextEditCommandEnabled(
    ui::TextEditCommand command) const {
  return false;
}

ukm::SourceId ArcImeService::GetClientSourceForMetrics() const {
  // TODO(yhanada): Implement this method. crbug.com/752657
  NOTIMPLEMENTED_LOG_ONCE();
  return ukm::SourceId();
}

bool ArcImeService::ShouldDoLearning() {
  return is_personalized_learning_allowed_;
}

bool ArcImeService::SetCompositionFromExistingText(
    const gfx::Range& range,
    const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) {
  if (text_range_.IsValid() && !range.IsBoundedBy(text_range_))
    return false;

  InvalidateSurroundingTextAndSelectionRange();
  has_composition_text_ = !range.is_empty();

  // The sent |range| might be already invalid if the textfield state in Android
  // side is changed simultaneously. It's okay because InputConnection's
  // setComposingRegion handles invalid region correctly.
  ime_bridge_->SendSetComposingRegion(range);
  return true;
}

gfx::Range ArcImeService::GetAutocorrectRange() const {
  // TODO(crbug.com/40134032): Implement this method.
  return gfx::Range();
}

gfx::Rect ArcImeService::GetAutocorrectCharacterBounds() const {
  // TODO(crbug.com/40623107): Implement this method.
  NOTIMPLEMENTED_LOG_ONCE();
  return gfx::Rect();
}

bool ArcImeService::SetAutocorrectRange(const gfx::Range& range) {
  if (!range.is_empty()) {
    base::UmaHistogramEnumeration("InputMethod.Assistive.Autocorrect.Count",
                                  TextInputClient::SubClass::kArcImeService);
  }
  // TODO(crbug.com/40134032): Implement this method.
  NOTIMPLEMENTED_LOG_ONCE();
  return false;
}

std::optional<ui::GrammarFragment> ArcImeService::GetGrammarFragmentAtCursor()
    const {
  // TODO(crbug.com/40178699): Implement this method.
  NOTIMPLEMENTED_LOG_ONCE();
  return std::nullopt;
}

bool ArcImeService::ClearGrammarFragments(const gfx::Range& range) {
  // TODO(crbug.com/40178699): Implement this method.
  NOTIMPLEMENTED_LOG_ONCE();
  return false;
}

bool ArcImeService::AddGrammarFragments(
    const std::vector<ui::GrammarFragment>& fragments) {
  if (!fragments.empty()) {
    base::UmaHistogramEnumeration("InputMethod.Assistive.Grammar.Count",
                                  TextInputClient::SubClass::kArcImeService);
  }
  // TODO(crbug.com/40178699): Implement this method.
  NOTIMPLEMENTED_LOG_ONCE();
  return false;
}

void ArcImeService::OnDispatchingKeyEventPostIME(ui::KeyEvent* event) {
  if (receiver_->HasCallback() && receiver_->DispatchKeyEventPostIME(event)) {
    event->SetHandled();
    return;
  }

  // Do not forward the key event from virtual keyboard if it's sent via
  // InsertChar(). By the special logic in
  // `ash::InputMethodAsh::DispatchKeyEvent`, both of InsertChar() and
  // DispatchKeyEventPostIME() are called for a key event injected by the
  // virtual keyboard. The below logic stops key event propagation through
  // DispatchKeyEventPostIME() to prevent from inputting two characters.
  const bool from_vk =
      event->properties() && (event->properties()->find(ui::kPropertyFromVK) !=
                              event->properties()->end());
  if (from_vk && IsCharacterKeyEvent(event) && IsTextInputActive(ime_type_))
    event->SetHandled();

  // Do not forward the language input key event from virtual keyboard because
  // it's already handled by `ash::InputMethodAsh`.
  if (from_vk && IsLanguageInputKey(event))
    event->SetHandled();

  // Do no forward a fabricated key event which is not originated from a
  // physical key event. Such a key event is a signal from IME to show they are
  // going to insert/delete text. ARC apps should not see any key event caused
  // by it.
  if (event->key_code() == ui::VKEY_PROCESSKEY && IsTextInputActive(ime_type_))
    event->SetHandled();
}

// static
void ArcImeService::SetOverrideDefaultDeviceScaleFactorForTesting(
    std::optional<double> scale_factor) {
  g_override_default_device_scale_factor = scale_factor;
}

// static
void ArcImeService::SetOverrideDisplayOriginForTesting(
    std::optional<gfx::Point> origin) {
  g_override_display_origin = origin;
}

void ArcImeService::InvalidateSurroundingTextAndSelectionRange() {
  text_range_ = gfx::Range::InvalidRange();
  text_in_range_ = std::u16string();
  selection_range_ = gfx::Range::InvalidRange();
}

bool ArcImeService::UpdateCursorRect(
    const gfx::Rect& rect,
    mojom::CursorCoordinateSpace coordinate_space) {
  gfx::Rect converted;
  if (coordinate_space == mojom::CursorCoordinateSpace::NOTIFICATION) {
    if (!focused_arc_window_)
      return false;

    // Rect is always scaled by the default device scale factor for
    // notification windows.
    converted =
        gfx::ScaleToEnclosingRect(rect, 1.0 / GetDefaultDeviceScaleFactor());

    // Convert the rect from a "notification display" coordinate into the window
    // coordinate. Because notification are aligned in horizontally on the
    // Android side, we just divide x coordinate by the width of the
    // notification window.
    converted.set_x(
        converted.x() %
        ash::ArcNotificationContentView::GetNotificationContentViewWidth());

    // Convert the window coordinate into the screen coordinate.
    converted.Offset(
        focused_arc_window_->GetBoundsInScreen().OffsetFromOrigin());
  } else if (focused_arc_window_) {
    // Convert from Android pixels to Chrome DIP.
    converted = gfx::ScaleToEnclosingRect(
        rect, 1.0 / GetDeviceScaleFactorForFocusedWindow());

    if (coordinate_space == mojom::CursorCoordinateSpace::DISPLAY) {
      // Convert into the screen coordinate.
      const gfx::Point display_origin = GetDisplayOriginForFocusedWindow();
      converted.Offset(display_origin.x(), display_origin.y());
    }

    auto* window = focused_arc_window_->GetToplevelWindow();
    auto* widget = views::Widget::GetWidgetForNativeWindow(window);
    // Check fullscreen window as well because it's possible for ARC to request
    // frame regardless of window state.
    bool covers_display =
        widget && (widget->IsMaximized() || widget->IsFullscreen());
    if (covers_display) {
      auto* frame_view = widget->non_client_view()->frame_view();
      // The frame height will be subtracted from client bounds.
      gfx::Rect bounds =
          frame_view->GetWindowBoundsForClientBounds(gfx::Rect());
      converted.Offset(0, -bounds.y());
    }
  }

  if (cursor_rect_ == converted)
    return false;
  cursor_rect_ = converted;
  return true;
}

bool ArcImeService::ShouldSendUpdateToInputMethod() const {
  // New text input state received from Android should not be sent to
  // InputMethod when the focus is on a non-ARC window. Text input state updates
  // can be sent from Android anytime because there is a dummy input view in
  // Android which is synchronized with the text input on a non-ARC window.
  return focused_arc_window_ != nullptr;
}

double ArcImeService::GetDeviceScaleFactorForKeyboard() const {
  if (g_override_default_device_scale_factor.has_value())
    return g_override_default_device_scale_factor.value();
  if (!exo::WMHelper::HasInstance() ||
      !keyboard::KeyboardUIController::HasInstance()) {
    return 1.0;
  }
  aura::Window* const keyboard_window =
      keyboard::KeyboardUIController::Get()->GetKeyboardWindow();
  if (!keyboard_window)
    return 1.0;
  return exo::WMHelper::GetInstance()->GetDeviceScaleFactorForWindow(
      keyboard_window);
}

double ArcImeService::GetDeviceScaleFactorForFocusedWindow() const {
  DCHECK(focused_arc_window_);
  if (g_override_default_device_scale_factor.has_value())
    return g_override_default_device_scale_factor.value();
  if (!exo::WMHelper::HasInstance())
    return 1.0;
  return exo::WMHelper::GetInstance()->GetDeviceScaleFactorForWindow(
      focused_arc_window_);
}

double ArcImeService::GetDefaultDeviceScaleFactor() const {
  if (g_override_default_device_scale_factor.has_value())
    return g_override_default_device_scale_factor.value();
  if (!exo::WMHelper::HasInstance())
    return 1.0;
  return exo::GetDefaultDeviceScaleFactor();
}

gfx::Point ArcImeService::GetDisplayOriginForFocusedWindow() const {
  DCHECK(focused_arc_window_);
  if (g_override_display_origin.has_value())
    return g_override_display_origin.value();
  return display::Screen::GetScreen()
      ->GetDisplayNearestWindow(focused_arc_window_)
      .bounds()
      .origin();
}

// static
void ArcImeService::EnsureFactoryBuilt() {
  ArcImeServiceFactory::GetInstance();
}

}  // namespace arc