
So that auth_panel does not need to depend on Shell and its deps. e.g. //chromeos/ash/services/bluetooth_config/public/mojom Bug: b:354839582 Change-Id: Iaa01cc6c610e3152015070c5f6eea630768fcd5a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5753983 Reviewed-by: Denis Kuznetsov <antrim@chromium.org> Commit-Queue: Xiyuan Xia <xiyuan@chromium.org> Reviewed-by: Yuichiro Hanada <yhanada@chromium.org> Reviewed-by: Xiyuan Xia <xiyuan@chromium.org> Auto-Submit: Xiyuan Xia <xiyuan@chromium.org> Cr-Commit-Position: refs/heads/main@{#1336041}
374 lines
14 KiB
C++
374 lines
14 KiB
C++
// 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 <vector>
|
|
|
|
#include "ash/ime/mode_indicator_observer.h"
|
|
#include "ash/ime/test_ime_controller_client.h"
|
|
#include "ash/public/cpp/ime_info.h"
|
|
#include "ash/shell.h"
|
|
#include "ash/system/ime/ime_observer.h"
|
|
#include "ash/system/tray/system_tray_notifier.h"
|
|
#include "ash/test/ash_test_base.h"
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "ui/base/accelerators/accelerator.h"
|
|
#include "ui/base/ime/ash/extension_ime_util.h"
|
|
#include "ui/display/manager/display_manager.h"
|
|
#include "ui/events/keycodes/keyboard_codes.h"
|
|
#include "ui/views/widget/widget.h"
|
|
|
|
namespace ash {
|
|
namespace {
|
|
|
|
// 43 is the designed size of the inner contents.
|
|
// This value corresponds with kMinSize defined in
|
|
// mode_indicator_delegate_view.cc.
|
|
const int kInnerSize = 43;
|
|
|
|
// Refreshes the IME list with fake IMEs and fake menu items.
|
|
void RefreshImesWithMenuItems(const std::string& current_ime_id,
|
|
const std::vector<std::string>& ime_ids,
|
|
const std::vector<std::string>& menu_item_keys) {
|
|
std::vector<ImeInfo> available_imes;
|
|
for (const std::string& ime_id : ime_ids) {
|
|
ImeInfo ime;
|
|
ime.id = ime_id;
|
|
available_imes.push_back(std::move(ime));
|
|
}
|
|
std::vector<ImeMenuItem> menu_items;
|
|
for (const std::string& menu_item_key : menu_item_keys) {
|
|
ImeMenuItem item;
|
|
item.key = menu_item_key;
|
|
menu_items.push_back(std::move(item));
|
|
}
|
|
Shell::Get()->ime_controller()->RefreshIme(
|
|
current_ime_id, std::move(available_imes), std::move(menu_items));
|
|
}
|
|
|
|
// Refreshes the IME list without adding any menu items.
|
|
void RefreshImes(const std::string& current_ime_id,
|
|
const std::vector<std::string>& ime_ids) {
|
|
const std::vector<std::string> empty_menu_items;
|
|
RefreshImesWithMenuItems(current_ime_id, ime_ids, empty_menu_items);
|
|
}
|
|
|
|
class TestImeObserver : public IMEObserver {
|
|
public:
|
|
TestImeObserver() = default;
|
|
~TestImeObserver() override = default;
|
|
|
|
// IMEObserver:
|
|
void OnIMERefresh() override { ++refresh_count_; }
|
|
void OnIMEMenuActivationChanged(bool is_active) override {
|
|
ime_menu_active_ = is_active;
|
|
}
|
|
|
|
int refresh_count_ = 0;
|
|
bool ime_menu_active_ = false;
|
|
};
|
|
|
|
class TestImeControllerObserver : public ImeController::Observer {
|
|
public:
|
|
TestImeControllerObserver() = default;
|
|
|
|
TestImeControllerObserver(const TestImeControllerObserver&) = delete;
|
|
TestImeControllerObserver& operator=(const TestImeControllerObserver&) =
|
|
delete;
|
|
|
|
// IMEController::Observer:
|
|
void OnCapsLockChanged(bool enabled) override { ++caps_lock_count_; }
|
|
void OnKeyboardLayoutNameChanged(const std::string& layout_name) override {
|
|
last_keyboard_layout_name_ = layout_name;
|
|
}
|
|
|
|
const std::string& last_keyboard_layout_name() const {
|
|
return last_keyboard_layout_name_;
|
|
}
|
|
|
|
private:
|
|
int caps_lock_count_ = 0;
|
|
std::string last_keyboard_layout_name_;
|
|
};
|
|
|
|
using ImeControllerImplTest = AshTestBase;
|
|
|
|
TEST_F(ImeControllerImplTest, RefreshIme) {
|
|
ImeControllerImpl* controller = Shell::Get()->ime_controller();
|
|
TestImeObserver observer;
|
|
Shell::Get()->system_tray_notifier()->AddIMEObserver(&observer);
|
|
|
|
RefreshImesWithMenuItems("ime1", {"ime1", "ime2"}, {"menu1"});
|
|
|
|
// Cached data was updated.
|
|
EXPECT_EQ("ime1", controller->current_ime().id);
|
|
ASSERT_EQ(2u, controller->GetVisibleImes().size());
|
|
EXPECT_EQ("ime1", controller->GetVisibleImes()[0].id);
|
|
EXPECT_EQ("ime2", controller->GetVisibleImes()[1].id);
|
|
ASSERT_EQ(1u, controller->current_ime_menu_items().size());
|
|
EXPECT_EQ("menu1", controller->current_ime_menu_items()[0].key);
|
|
|
|
// Observer was notified.
|
|
EXPECT_EQ(1, observer.refresh_count_);
|
|
}
|
|
|
|
TEST_F(ImeControllerImplTest, NoCurrentIme) {
|
|
ImeControllerImpl* controller = Shell::Get()->ime_controller();
|
|
|
|
// Set up a single IME.
|
|
RefreshImes("ime1", {"ime1"});
|
|
EXPECT_EQ("ime1", controller->current_ime().id);
|
|
EXPECT_TRUE(controller->IsCurrentImeVisible());
|
|
|
|
// When there is no current IME the cached current IME is empty.
|
|
const std::string empty_ime_id;
|
|
RefreshImes(empty_ime_id, {"ime1"});
|
|
EXPECT_TRUE(controller->current_ime().id.empty());
|
|
}
|
|
|
|
TEST_F(ImeControllerImplTest, CurrentImeNotVisible) {
|
|
ImeControllerImpl* controller = Shell::Get()->ime_controller();
|
|
|
|
// Add only Dictation.
|
|
std::string dictation_id =
|
|
"_ext_ime_egfdjlfmgnehecnclamagfafdccgfndpdictation";
|
|
RefreshImes(dictation_id, {dictation_id});
|
|
EXPECT_EQ(dictation_id, controller->current_ime().id);
|
|
EXPECT_FALSE(controller->IsCurrentImeVisible());
|
|
EXPECT_EQ(0u, controller->GetVisibleImes().size());
|
|
|
|
// Add something else too, but Dictation is active.
|
|
RefreshImes(dictation_id, {dictation_id, "ime1"});
|
|
EXPECT_EQ(dictation_id, controller->current_ime().id);
|
|
EXPECT_FALSE(controller->IsCurrentImeVisible());
|
|
EXPECT_EQ(1u, controller->GetVisibleImes().size());
|
|
|
|
// Inactivate the other IME, leave Dictation in the list.
|
|
RefreshImes("ime1", {dictation_id, "ime1"});
|
|
EXPECT_EQ("ime1", controller->current_ime().id);
|
|
EXPECT_TRUE(controller->IsCurrentImeVisible());
|
|
EXPECT_EQ(1u, controller->GetVisibleImes().size());
|
|
}
|
|
|
|
TEST_F(ImeControllerImplTest, SetImesManagedByPolicy) {
|
|
ImeControllerImpl* controller = Shell::Get()->ime_controller();
|
|
TestImeObserver observer;
|
|
Shell::Get()->system_tray_notifier()->AddIMEObserver(&observer);
|
|
|
|
// Defaults to false.
|
|
EXPECT_FALSE(controller->managed_by_policy());
|
|
|
|
// Setting the value notifies observers.
|
|
controller->SetImesManagedByPolicy(true);
|
|
EXPECT_TRUE(controller->managed_by_policy());
|
|
EXPECT_EQ(1, observer.refresh_count_);
|
|
}
|
|
|
|
TEST_F(ImeControllerImplTest, ShowImeMenuOnShelf) {
|
|
ImeControllerImpl* controller = Shell::Get()->ime_controller();
|
|
TestImeObserver observer;
|
|
Shell::Get()->system_tray_notifier()->AddIMEObserver(&observer);
|
|
|
|
controller->ShowImeMenuOnShelf(true);
|
|
EXPECT_TRUE(observer.ime_menu_active_);
|
|
}
|
|
|
|
TEST_F(ImeControllerImplTest, CanSwitchIme) {
|
|
ImeControllerImpl* controller = Shell::Get()->ime_controller();
|
|
|
|
// Can't switch IMEs when none are available.
|
|
ASSERT_EQ(0u, controller->GetVisibleImes().size());
|
|
EXPECT_FALSE(controller->CanSwitchIme());
|
|
|
|
// Can't switch with only 1 IME.
|
|
RefreshImes("ime1", {"ime1"});
|
|
EXPECT_FALSE(controller->CanSwitchIme());
|
|
|
|
// Can switch with more than 1 IME.
|
|
RefreshImes("ime1", {"ime1", "ime2"});
|
|
EXPECT_TRUE(controller->CanSwitchIme());
|
|
}
|
|
|
|
TEST_F(ImeControllerImplTest, SwitchIme) {
|
|
ImeControllerImpl* controller = Shell::Get()->ime_controller();
|
|
TestImeControllerClient client;
|
|
|
|
// Can't switch IME before the client is set.
|
|
controller->SwitchToNextIme();
|
|
EXPECT_EQ(0, client.next_ime_count_);
|
|
|
|
controller->SwitchToLastUsedIme();
|
|
EXPECT_EQ(0, client.last_used_ime_count_);
|
|
|
|
controller->SwitchImeById("ime1", true /* show_message */);
|
|
EXPECT_EQ(0, client.switch_ime_count_);
|
|
|
|
// After setting the client the requests are forwarded.
|
|
controller->SetClient(&client);
|
|
controller->SwitchToNextIme();
|
|
EXPECT_EQ(1, client.next_ime_count_);
|
|
|
|
controller->SwitchToLastUsedIme();
|
|
EXPECT_EQ(1, client.last_used_ime_count_);
|
|
|
|
controller->SwitchImeById("ime1", true /* show_message */);
|
|
EXPECT_EQ(1, client.switch_ime_count_);
|
|
EXPECT_EQ("ime1", client.last_switch_ime_id_);
|
|
EXPECT_TRUE(client.last_show_message_);
|
|
|
|
controller->SwitchImeById("ime2", false /* show_message */);
|
|
EXPECT_EQ(2, client.switch_ime_count_);
|
|
EXPECT_EQ("ime2", client.last_switch_ime_id_);
|
|
EXPECT_FALSE(client.last_show_message_);
|
|
}
|
|
|
|
TEST_F(ImeControllerImplTest, SwitchImeWithAccelerator) {
|
|
ImeControllerImpl* controller = Shell::Get()->ime_controller();
|
|
TestImeControllerClient client;
|
|
controller->SetClient(&client);
|
|
|
|
const ui::Accelerator convert(ui::VKEY_CONVERT, ui::EF_NONE);
|
|
const ui::Accelerator non_convert(ui::VKEY_NONCONVERT, ui::EF_NONE);
|
|
const ui::Accelerator wide_half_1(ui::VKEY_DBE_SBCSCHAR, ui::EF_NONE);
|
|
const ui::Accelerator wide_half_2(ui::VKEY_DBE_DBCSCHAR, ui::EF_NONE);
|
|
|
|
// When there are no IMEs available switching by accelerator does not work.
|
|
ASSERT_EQ(0u, controller->GetVisibleImes().size());
|
|
EXPECT_FALSE(controller->CanSwitchImeWithAccelerator(convert));
|
|
EXPECT_FALSE(controller->CanSwitchImeWithAccelerator(non_convert));
|
|
EXPECT_FALSE(controller->CanSwitchImeWithAccelerator(wide_half_1));
|
|
EXPECT_FALSE(controller->CanSwitchImeWithAccelerator(wide_half_2));
|
|
|
|
// With only test IMEs (and no Japanese IMEs) the special keys do not work.
|
|
RefreshImes("ime1", {"ime1", "ime2"});
|
|
EXPECT_FALSE(controller->CanSwitchImeWithAccelerator(convert));
|
|
EXPECT_FALSE(controller->CanSwitchImeWithAccelerator(non_convert));
|
|
EXPECT_FALSE(controller->CanSwitchImeWithAccelerator(wide_half_1));
|
|
EXPECT_FALSE(controller->CanSwitchImeWithAccelerator(wide_half_2));
|
|
|
|
// Install both a test IME and Japanese IMEs.
|
|
using extension_ime_util::GetInputMethodIDByEngineID;
|
|
const std::string nacl_mozc_jp = GetInputMethodIDByEngineID("nacl_mozc_jp");
|
|
const std::string xkb_jp_jpn = GetInputMethodIDByEngineID("xkb:jp::jpn");
|
|
RefreshImes("ime1", {"ime1", nacl_mozc_jp, xkb_jp_jpn});
|
|
|
|
// Accelerator based switching now works.
|
|
EXPECT_TRUE(controller->CanSwitchImeWithAccelerator(convert));
|
|
EXPECT_TRUE(controller->CanSwitchImeWithAccelerator(non_convert));
|
|
EXPECT_TRUE(controller->CanSwitchImeWithAccelerator(wide_half_1));
|
|
EXPECT_TRUE(controller->CanSwitchImeWithAccelerator(wide_half_2));
|
|
|
|
// Convert keys jump directly to the requested IME.
|
|
controller->SwitchImeWithAccelerator(convert);
|
|
EXPECT_EQ(1, client.switch_ime_count_);
|
|
EXPECT_EQ(nacl_mozc_jp, client.last_switch_ime_id_);
|
|
|
|
controller->SwitchImeWithAccelerator(non_convert);
|
|
EXPECT_EQ(2, client.switch_ime_count_);
|
|
EXPECT_EQ(xkb_jp_jpn, client.last_switch_ime_id_);
|
|
|
|
// Switch from nacl_mozc_jp to xkb_jp_jpn.
|
|
RefreshImes(nacl_mozc_jp, {"ime1", nacl_mozc_jp, xkb_jp_jpn});
|
|
controller->SwitchImeWithAccelerator(wide_half_1);
|
|
EXPECT_EQ(3, client.switch_ime_count_);
|
|
EXPECT_EQ(xkb_jp_jpn, client.last_switch_ime_id_);
|
|
|
|
// Switch from xkb_jp_jpn to nacl_mozc_jp.
|
|
RefreshImes(xkb_jp_jpn, {"ime1", nacl_mozc_jp, xkb_jp_jpn});
|
|
controller->SwitchImeWithAccelerator(wide_half_2);
|
|
EXPECT_EQ(4, client.switch_ime_count_);
|
|
EXPECT_EQ(nacl_mozc_jp, client.last_switch_ime_id_);
|
|
}
|
|
|
|
TEST_F(ImeControllerImplTest, SetCapsLock) {
|
|
ImeControllerImpl* controller = Shell::Get()->ime_controller();
|
|
TestImeControllerClient client;
|
|
EXPECT_EQ(0, client.set_caps_lock_count_);
|
|
|
|
controller->SetCapsLockEnabled(true);
|
|
EXPECT_EQ(0, client.set_caps_lock_count_);
|
|
|
|
controller->SetClient(&client);
|
|
|
|
controller->SetCapsLockEnabled(true);
|
|
EXPECT_EQ(1, client.set_caps_lock_count_);
|
|
// Does not no-op when the state is the same. Should send all notifications.
|
|
controller->SetCapsLockEnabled(true);
|
|
EXPECT_EQ(2, client.set_caps_lock_count_);
|
|
controller->SetCapsLockEnabled(false);
|
|
EXPECT_EQ(3, client.set_caps_lock_count_);
|
|
controller->SetCapsLockEnabled(false);
|
|
EXPECT_EQ(4, client.set_caps_lock_count_);
|
|
|
|
EXPECT_FALSE(controller->IsCapsLockEnabled());
|
|
controller->UpdateCapsLockState(true);
|
|
EXPECT_TRUE(controller->IsCapsLockEnabled());
|
|
controller->UpdateCapsLockState(false);
|
|
EXPECT_FALSE(controller->IsCapsLockEnabled());
|
|
}
|
|
|
|
TEST_F(ImeControllerImplTest, OnKeyboardLayoutNameChanged) {
|
|
ImeControllerImpl* controller = Shell::Get()->ime_controller();
|
|
EXPECT_TRUE(controller->keyboard_layout_name().empty());
|
|
|
|
TestImeControllerObserver observer;
|
|
controller->AddObserver(&observer);
|
|
controller->OnKeyboardLayoutNameChanged("us(dvorak)");
|
|
EXPECT_EQ("us(dvorak)", controller->keyboard_layout_name());
|
|
EXPECT_EQ("us(dvorak)", observer.last_keyboard_layout_name());
|
|
}
|
|
|
|
TEST_F(ImeControllerImplTest, ShowModeIndicator) {
|
|
ImeControllerImpl* controller = Shell::Get()->ime_controller();
|
|
std::u16string text = u"US";
|
|
|
|
gfx::Rect cursor1_bounds(100, 100, 1, 20);
|
|
controller->ShowModeIndicator(cursor1_bounds, text);
|
|
|
|
views::Widget* widget1 =
|
|
controller->mode_indicator_observer()->active_widget();
|
|
EXPECT_TRUE(widget1);
|
|
|
|
// The widget bounds should be bigger than the inner size.
|
|
gfx::Rect bounds1 = widget1->GetWindowBoundsInScreen();
|
|
EXPECT_LE(kInnerSize, bounds1.width());
|
|
EXPECT_LE(kInnerSize, bounds1.height());
|
|
|
|
gfx::Rect cursor2_bounds(50, 200, 1, 20);
|
|
controller->ShowModeIndicator(cursor2_bounds, text);
|
|
|
|
views::Widget* widget2 =
|
|
controller->mode_indicator_observer()->active_widget();
|
|
EXPECT_TRUE(widget2);
|
|
EXPECT_NE(widget1, widget2);
|
|
|
|
// Check if the location of the mode indicator corresponds to the cursor
|
|
// bounds.
|
|
gfx::Rect bounds2 = widget2->GetWindowBoundsInScreen();
|
|
EXPECT_EQ(cursor1_bounds.x() - cursor2_bounds.x(), bounds1.x() - bounds2.x());
|
|
EXPECT_EQ(cursor1_bounds.y() - cursor2_bounds.y(), bounds1.y() - bounds2.y());
|
|
EXPECT_EQ(bounds1.width(), bounds2.width());
|
|
EXPECT_EQ(bounds1.height(), bounds2.height());
|
|
|
|
const gfx::Rect screen_bounds = display::Screen::GetScreen()
|
|
->GetDisplayMatching(cursor1_bounds)
|
|
.work_area();
|
|
const gfx::Rect cursor3_bounds(100, screen_bounds.bottom() - 25, 1, 20);
|
|
controller->ShowModeIndicator(cursor3_bounds, text);
|
|
|
|
views::Widget* widget3 =
|
|
controller->mode_indicator_observer()->active_widget();
|
|
EXPECT_TRUE(widget3);
|
|
EXPECT_NE(widget2, widget3);
|
|
|
|
// Check if the location of the mode indicator is considered with the screen
|
|
// size.
|
|
gfx::Rect bounds3 = widget3->GetWindowBoundsInScreen();
|
|
EXPECT_LT(bounds3.bottom(), screen_bounds.bottom());
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace ash
|