blob: c98e0f0915618ec051e45e2486ec9f08f939ee3a [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service.h"
#include <memory>
#include <tuple>
#include <utility>
#include <vector>
#include "ash/public/cpp/ash_pref_names.h"
#include "ash/public/cpp/keyboard/keyboard_switches.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/chromeos/arc/input_method_manager/test_input_method_manager_bridge.h"
#include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client_test_helper.h"
#include "chrome/browser/ui/ash/tablet_mode_client.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/chrome_ash_test_base.h"
#include "chrome/test/base/testing_profile.h"
#include "components/arc/arc_service_manager.h"
#include "components/arc/test/test_browser_context.h"
#include "components/crx_file/id_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ime/chromeos/extension_ime_util.h"
#include "ui/base/ime/chromeos/mock_input_method_manager.h"
#include "ui/base/ime/dummy_text_input_client.h"
#include "ui/base/ime/ime_bridge.h"
#include "ui/base/ime/mock_ime_input_context_handler.h"
#include "ui/base/ime/mock_input_method.h"
namespace arc {
namespace {
namespace im = chromeos::input_method;
// The fake im::InputMethodManager for testing.
class TestInputMethodManager : public im::MockInputMethodManager {
public:
// The fake im::InputMethodManager::State implementation for testing.
class TestState : public im::MockInputMethodManager::State {
public:
TestState()
: added_input_method_extensions_(), active_input_method_ids_() {}
const std::vector<std::string>& GetActiveInputMethodIds() const override {
return active_input_method_ids_;
}
im::InputMethodDescriptor GetCurrentInputMethod() const override {
im::InputMethodDescriptor descriptor(
active_ime_id_, "", "", std::vector<std::string>(),
std::vector<std::string>(), false /* is_login_keyboard */, GURL(),
GURL());
return descriptor;
}
void AddInputMethodExtension(
const std::string& extension_id,
const im::InputMethodDescriptors& descriptors,
ui::IMEEngineHandlerInterface* instance) override {
added_input_method_extensions_.push_back(
std::make_tuple(extension_id, descriptors, instance));
}
void RemoveInputMethodExtension(const std::string& extension_id) override {
removed_input_method_extensions_.push_back(extension_id);
}
bool EnableInputMethod(
const std::string& new_active_input_method_id) override {
enabled_input_methods_.push_back(new_active_input_method_id);
return true;
}
void AddActiveInputMethodId(const std::string& ime_id) {
if (!std::count(active_input_method_ids_.begin(),
active_input_method_ids_.end(), ime_id)) {
active_input_method_ids_.push_back(ime_id);
}
}
void RemoveActiveInputMethodId(const std::string& ime_id) {
base::EraseIf(active_input_method_ids_,
[&ime_id](const std::string& id) { return id == ime_id; });
}
void SetActiveInputMethod(const std::string& ime_id) {
active_ime_id_ = ime_id;
}
void GetInputMethodExtensions(
im::InputMethodDescriptors* descriptors) override {
for (const auto& id : active_input_method_ids_) {
descriptors->push_back(im::InputMethodDescriptor(
id, "", "", {}, {}, false, GURL(), GURL()));
}
}
bool SetAllowedInputMethods(
const std::vector<std::string>& new_allowed_input_method_ids,
bool enable_allowed_input_methods) override {
allowed_input_methods_ = new_allowed_input_method_ids;
return true;
}
const std::vector<std::string>& GetAllowedInputMethods() override {
return allowed_input_methods_;
}
bool IsInputMethodAllowed(const std::string& ime_id) {
return allowed_input_methods_.empty() ||
base::Contains(allowed_input_methods_, ime_id);
}
std::vector<std::tuple<std::string,
im::InputMethodDescriptors,
ui::IMEEngineHandlerInterface*>>
added_input_method_extensions_;
std::vector<std::string> removed_input_method_extensions_;
std::vector<std::string> enabled_input_methods_;
protected:
friend base::RefCounted<InputMethodManager::State>;
~TestState() override = default;
private:
std::vector<std::string> active_input_method_ids_;
std::string active_ime_id_ = "";
std::vector<std::string> allowed_input_methods_;
};
TestInputMethodManager() {
state_ = scoped_refptr<TestState>(new TestState());
}
~TestInputMethodManager() override = default;
scoped_refptr<InputMethodManager::State> GetActiveIMEState() override {
return state_;
}
TestState* state() { return state_.get(); }
private:
scoped_refptr<TestState> state_;
DISALLOW_COPY_AND_ASSIGN(TestInputMethodManager);
};
class TestIMEInputContextHandler : public ui::MockIMEInputContextHandler {
public:
explicit TestIMEInputContextHandler(ui::InputMethod* input_method)
: input_method_(input_method) {}
ui::InputMethod* GetInputMethod() override { return input_method_; }
private:
ui::InputMethod* const input_method_;
DISALLOW_COPY_AND_ASSIGN(TestIMEInputContextHandler);
};
// TODO(crbug.com/890677): Stop inheriting ChromeAshTestBase once ash::Shell
// dependency is removed from ArcInputMethodManagerService.
class ArcInputMethodManagerServiceTest : public ChromeAshTestBase {
protected:
ArcInputMethodManagerServiceTest()
: arc_service_manager_(std::make_unique<ArcServiceManager>()) {}
~ArcInputMethodManagerServiceTest() override = default;
ArcInputMethodManagerService* service() { return service_; }
TestInputMethodManagerBridge* bridge() { return test_bridge_; }
TestInputMethodManager* imm() { return input_method_manager_; }
TestingProfile* profile() { return profile_.get(); }
void ToggleTabletMode(bool enabled) {
tablet_mode_client_->OnTabletModeToggled(enabled);
}
void SetUp() override {
ChromeAshTestBase::SetUp();
ui::IMEBridge::Initialize();
input_method_manager_ = new TestInputMethodManager();
chromeos::input_method::InputMethodManager::Initialize(
input_method_manager_);
profile_ = std::make_unique<TestingProfile>();
tablet_mode_client_ = std::make_unique<TabletModeClient>();
chrome_keyboard_controller_client_test_helper_ =
ChromeKeyboardControllerClientTestHelper::InitializeForAsh();
chrome_keyboard_controller_client_test_helper_->SetProfile(profile_.get());
service_ = ArcInputMethodManagerService::GetForBrowserContextForTesting(
profile_.get());
test_bridge_ = new TestInputMethodManagerBridge();
service_->SetInputMethodManagerBridgeForTesting(
base::WrapUnique(test_bridge_));
}
void TearDown() override {
test_bridge_ = nullptr;
service_->Shutdown();
profile_.reset();
chromeos::input_method::InputMethodManager::Shutdown();
ui::IMEBridge::Shutdown();
ChromeAshTestBase::TearDown();
// Needs to be after ash::Shell is destroyed, as
// |chrome_keyboard_controller_client_test_helper_| observes the keyboard
// destruction.
chrome_keyboard_controller_client_test_helper_.reset();
// To match ChromeBrowserMainExtraPartsAsh, shut down the TabletModeClient
// after Shell.
tablet_mode_client_.reset();
}
private:
std::unique_ptr<ArcServiceManager> arc_service_manager_;
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<TabletModeClient> tablet_mode_client_;
std::unique_ptr<ChromeKeyboardControllerClientTestHelper>
chrome_keyboard_controller_client_test_helper_;
TestInputMethodManager* input_method_manager_ = nullptr;
TestInputMethodManagerBridge* test_bridge_ = nullptr; // Owned by |service_|
ArcInputMethodManagerService* service_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(ArcInputMethodManagerServiceTest);
};
} // anonymous namespace
TEST_F(ArcInputMethodManagerServiceTest, EnableIme) {
namespace ceiu = chromeos::extension_ime_util;
using crx_file::id_util::GenerateId;
ToggleTabletMode(true);
ASSERT_EQ(0u, bridge()->enable_ime_calls_.size());
const std::string extension_ime_id =
ceiu::GetInputMethodID(GenerateId("test.extension.ime"), "us");
const std::string component_extension_ime_id =
ceiu::GetComponentInputMethodID(
GenerateId("test.component.extension.ime"), "us");
const std::string arc_ime_id =
ceiu::GetArcInputMethodID(GenerateId("test.arc.ime"), "us");
// EnableIme is called only when ARC IME is enable or disabled.
imm()->state()->AddActiveInputMethodId(extension_ime_id);
service()->ImeMenuListChanged();
EXPECT_EQ(0u, bridge()->enable_ime_calls_.size());
imm()->state()->AddActiveInputMethodId(component_extension_ime_id);
service()->ImeMenuListChanged();
EXPECT_EQ(0u, bridge()->enable_ime_calls_.size());
// Enable the ARC IME and verify that EnableIme is called.
imm()->state()->AddActiveInputMethodId(arc_ime_id);
service()->ImeMenuListChanged();
ASSERT_EQ(1u, bridge()->enable_ime_calls_.size());
EXPECT_EQ(ceiu::GetComponentIDByInputMethodID(arc_ime_id),
std::get<std::string>(bridge()->enable_ime_calls_[0]));
EXPECT_TRUE(std::get<bool>(bridge()->enable_ime_calls_[0]));
// Disable the ARC IME and verify that EnableIme is called with false.
imm()->state()->RemoveActiveInputMethodId(arc_ime_id);
service()->ImeMenuListChanged();
ASSERT_EQ(2u, bridge()->enable_ime_calls_.size());
EXPECT_EQ(ceiu::GetComponentIDByInputMethodID(arc_ime_id),
std::get<std::string>(bridge()->enable_ime_calls_[1]));
EXPECT_FALSE(std::get<bool>(bridge()->enable_ime_calls_[1]));
// EnableIme is not called when non ARC IME is disabled.
imm()->state()->RemoveActiveInputMethodId(extension_ime_id);
service()->ImeMenuListChanged();
EXPECT_EQ(2u, bridge()->enable_ime_calls_.size());
}
TEST_F(ArcInputMethodManagerServiceTest, EnableIme_WithPrefs) {
namespace ceiu = chromeos::extension_ime_util;
using crx_file::id_util::GenerateId;
ToggleTabletMode(true);
ASSERT_EQ(0u, bridge()->enable_ime_calls_.size());
const std::string component_extension_ime_id =
ceiu::GetComponentInputMethodID(
GenerateId("test.component.extension.ime"), "us");
const std::string arc_ime_id =
ceiu::GetArcInputMethodID(GenerateId("test.arc.ime"), "us");
imm()->state()->AddActiveInputMethodId(component_extension_ime_id);
service()->ImeMenuListChanged();
EXPECT_EQ(0u, bridge()->enable_ime_calls_.size());
imm()->state()->AddActiveInputMethodId(arc_ime_id);
service()->ImeMenuListChanged();
ASSERT_EQ(1u, bridge()->enable_ime_calls_.size());
// Test the case where |arc_ime_id| is temporarily disallowed because of the
// toggling to the laptop mode. In that case, the prefs still have the IME's
// ID.
profile()->GetPrefs()->SetString(
prefs::kLanguageEnabledImes,
base::StringPrintf("%s,%s", component_extension_ime_id.c_str(),
arc_ime_id.c_str()));
imm()->state()->RemoveActiveInputMethodId(arc_ime_id);
service()->ImeMenuListChanged();
// Verify that EnableIme(id, false) is NOT called.
EXPECT_EQ(1u, bridge()->enable_ime_calls_.size()); // still 1u, not 2u.
}
TEST_F(ArcInputMethodManagerServiceTest, SwitchImeTo) {
namespace ceiu = chromeos::extension_ime_util;
using crx_file::id_util::GenerateId;
const std::string arc_ime_service_id =
"org.chromium.arc.ime/.ArcInputMethodService";
ToggleTabletMode(true);
ASSERT_EQ(0u, bridge()->switch_ime_to_calls_.size());
const std::string extension_ime_id =
ceiu::GetInputMethodID(GenerateId("test.extension.ime"), "us");
const std::string component_extension_ime_id =
ceiu::GetComponentInputMethodID(
GenerateId("test.component.extension.ime"), "us");
const std::string arc_ime_id = ceiu::GetArcInputMethodID(
GenerateId("test.arc.ime"), "ime.id.in.arc.container");
// Set active input method to the extension ime.
imm()->state()->SetActiveInputMethod(extension_ime_id);
service()->InputMethodChanged(imm(), nullptr, false /* show_message */);
// ArcImeService should be selected.
ASSERT_EQ(1u, bridge()->switch_ime_to_calls_.size());
EXPECT_EQ(arc_ime_service_id, bridge()->switch_ime_to_calls_[0]);
// Set active input method to the component extension ime.
imm()->state()->SetActiveInputMethod(component_extension_ime_id);
service()->InputMethodChanged(imm(), nullptr, false /* show_message */);
// ArcImeService should be selected.
ASSERT_EQ(2u, bridge()->switch_ime_to_calls_.size());
EXPECT_EQ(arc_ime_service_id, bridge()->switch_ime_to_calls_[1]);
// Set active input method to the arc ime.
imm()->state()->SetActiveInputMethod(arc_ime_id);
service()->InputMethodChanged(imm(), nullptr, false /* show_message */);
ASSERT_EQ(3u, bridge()->switch_ime_to_calls_.size());
EXPECT_EQ("ime.id.in.arc.container", bridge()->switch_ime_to_calls_[2]);
}
TEST_F(ArcInputMethodManagerServiceTest, OnImeDisabled) {
namespace ceiu = chromeos::extension_ime_util;
constexpr char kNonArcIme[] = "ime_a";
constexpr char kArcImeX[] = "arc_ime_x";
constexpr char kArcImeY[] = "arc_ime_y";
constexpr char kArcIMEProxyExtensionName[] =
"org.chromium.arc.inputmethod.proxy";
const std::string proxy_ime_extension_id =
crx_file::id_util::GenerateId(kArcIMEProxyExtensionName);
const std::string arc_ime_x_component =
ceiu::GetArcInputMethodID(proxy_ime_extension_id, kArcImeX);
const std::string arc_ime_y_component =
ceiu::GetArcInputMethodID(proxy_ime_extension_id, kArcImeY);
// Enable one non-ARC IME, then remove an ARC IME. This usually does not
// happen, but confirm that OnImeDisabled() does not do anything bad even
// if the IPC is called that way.
profile()->GetPrefs()->SetString(prefs::kLanguageEnabledImes, kNonArcIme);
service()->OnImeDisabled(kArcImeX);
EXPECT_EQ(kNonArcIme,
profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes));
// Enable two IMEs (one non-ARC and one ARC), remove the ARC IME, and then
// confirm the non-ARC one remains.
std::string pref_str =
base::StringPrintf("%s,%s", kNonArcIme, arc_ime_x_component.c_str());
profile()->GetPrefs()->SetString(prefs::kLanguageEnabledImes, pref_str);
service()->OnImeDisabled(kArcImeX);
EXPECT_EQ(kNonArcIme,
profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes));
// Enable two ARC IMEs along with one non-ARC one, remove one of two ARC IMEs,
// then confirm one non-ARC IME and one ARC IME still remain.
pref_str = base::StringPrintf("%s,%s,%s", arc_ime_x_component.c_str(),
kNonArcIme, arc_ime_y_component.c_str());
profile()->GetPrefs()->SetString(prefs::kLanguageEnabledImes, pref_str);
service()->OnImeDisabled(kArcImeX);
pref_str =
base::StringPrintf("%s,%s", kNonArcIme, arc_ime_y_component.c_str());
EXPECT_EQ(pref_str,
profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes));
}
TEST_F(ArcInputMethodManagerServiceTest, OnImeInfoChanged) {
namespace ceiu = chromeos::extension_ime_util;
ToggleTabletMode(true);
// Preparing 2 ImeInfo.
const std::string android_ime_id1 = "test.arc.ime";
const std::string display_name1 = "DisplayName";
const std::string settings_url1 = "url_to_settings";
mojom::ImeInfoPtr info1 = mojom::ImeInfo::New();
info1->ime_id = android_ime_id1;
info1->display_name = display_name1;
info1->enabled = false;
info1->settings_url = settings_url1;
const std::string android_ime_id2 = "test.arc.ime2";
const std::string display_name2 = "DisplayName2";
const std::string settings_url2 = "url_to_settings2";
mojom::ImeInfoPtr info2 = mojom::ImeInfo::New();
info2->ime_id = android_ime_id2;
info2->display_name = display_name2;
info2->enabled = true;
info2->settings_url = settings_url2;
std::vector<
std::tuple<std::string, chromeos::input_method::InputMethodDescriptors,
ui::IMEEngineHandlerInterface*>>& added_extensions =
imm()->state()->added_input_method_extensions_;
ASSERT_EQ(0u, added_extensions.size());
{
// Passing empty info_array shouldn't call AddInputMethodExtension.
std::vector<mojom::ImeInfoPtr> info_array{};
service()->OnImeInfoChanged(std::move(info_array));
EXPECT_TRUE(added_extensions.empty());
}
{
// Adding one ARC IME.
std::vector<mojom::ImeInfoPtr> info_array;
info_array.emplace_back(info1.Clone());
service()->OnImeInfoChanged(std::move(info_array));
ASSERT_EQ(1u, added_extensions.size());
ASSERT_EQ(1u, std::get<1>(added_extensions[0]).size());
EXPECT_EQ(android_ime_id1, ceiu::GetComponentIDByInputMethodID(
std::get<1>(added_extensions[0])[0].id()));
EXPECT_EQ(display_name1, std::get<1>(added_extensions[0])[0].name());
ASSERT_EQ(1u, std::get<1>(added_extensions[0])[0].language_codes().size());
EXPECT_TRUE(chromeos::extension_ime_util::IsLanguageForArcIME(
(std::get<1>(added_extensions[0])[0].language_codes())[0]));
// Emulate enabling ARC IME from chrome://settings.
const std::string& arc_ime_id = std::get<1>(added_extensions[0])[0].id();
profile()->GetPrefs()->SetString(prefs::kLanguageEnabledImes, arc_ime_id);
EXPECT_EQ(arc_ime_id,
profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes));
// Removing the ARC IME should clear the pref
std::vector<mojom::ImeInfoPtr> empty_info_array;
service()->OnImeInfoChanged(std::move(empty_info_array));
EXPECT_TRUE(
profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes).empty());
added_extensions.clear();
}
{
// Adding two ARC IMEs. One is already enabled.
std::vector<mojom::ImeInfoPtr> info_array;
info_array.emplace_back(info1.Clone());
info_array.emplace_back(info2.Clone());
service()->OnImeInfoChanged(std::move(info_array));
// The ARC IMEs should be registered as two IMEs in one extension.
ASSERT_EQ(1u, added_extensions.size());
ASSERT_EQ(2u, std::get<1>(added_extensions[0]).size());
EXPECT_EQ(android_ime_id1, ceiu::GetComponentIDByInputMethodID(
std::get<1>(added_extensions[0])[0].id()));
EXPECT_EQ(display_name1, std::get<1>(added_extensions[0])[0].name());
EXPECT_EQ(android_ime_id2, ceiu::GetComponentIDByInputMethodID(
std::get<1>(added_extensions[0])[1].id()));
EXPECT_EQ(display_name2, std::get<1>(added_extensions[0])[1].name());
// Already enabled IME should be added to the pref automatically.
const std::string& arc_ime_id2 = std::get<1>(added_extensions[0])[1].id();
EXPECT_EQ(arc_ime_id2,
profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes));
added_extensions.clear();
}
}
TEST_F(ArcInputMethodManagerServiceTest, AllowArcIMEsOnlyInTabletMode) {
namespace ceiu = chromeos::extension_ime_util;
using crx_file::id_util::GenerateId;
const std::string extension_ime_id =
ceiu::GetInputMethodID(GenerateId("test.extension.ime"), "us");
const std::string component_extension_ime_id =
ceiu::GetComponentInputMethodID(
GenerateId("test.component.extension.ime"), "us");
const std::string arc_ime_id =
ceiu::GetArcInputMethodID(GenerateId("test.arc.ime"), "us");
// Start from tablet mode.
ToggleTabletMode(true);
// Activate 3 IMEs.
imm()->state()->AddActiveInputMethodId(extension_ime_id);
imm()->state()->AddActiveInputMethodId(component_extension_ime_id);
imm()->state()->AddActiveInputMethodId(arc_ime_id);
// Update the prefs because the testee checks them. Note that toggling the
// mode never changes the prefs.
profile()->GetPrefs()->SetString(
prefs::kLanguageEnabledImes,
base::StringPrintf("%s,%s,%s", extension_ime_id.c_str(),
component_extension_ime_id.c_str(),
arc_ime_id.c_str()));
// All IMEs are allowed to use.
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(component_extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(arc_ime_id));
// Change to laptop mode.
ToggleTabletMode(false);
// ARC IME is not allowed in laptop mode.
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(component_extension_ime_id));
EXPECT_FALSE(imm()->state()->IsInputMethodAllowed(arc_ime_id));
// Back to tablet mode.
EXPECT_TRUE(imm()->state()->enabled_input_methods_.empty());
ToggleTabletMode(true);
// All IMEs are allowed to use.
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(component_extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(arc_ime_id));
// Verify they appear in the CrOS IME menu.
ASSERT_EQ(1u, imm()->state()->enabled_input_methods_.size());
EXPECT_EQ(arc_ime_id, imm()->state()->enabled_input_methods_[0]);
// Do the same tests again, but with |extension_ime_id| disabled.
imm()->state()->SetAllowedInputMethods(
{component_extension_ime_id, arc_ime_id},
false /* enable_allowed_input_methods */);
ToggleTabletMode(false);
EXPECT_FALSE(imm()->state()->IsInputMethodAllowed(extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(component_extension_ime_id));
EXPECT_FALSE(imm()->state()->IsInputMethodAllowed(arc_ime_id));
ToggleTabletMode(true);
EXPECT_FALSE(imm()->state()->IsInputMethodAllowed(extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(component_extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(arc_ime_id));
// Confirm that entering the same mode twice in a row is no-op.
ToggleTabletMode(true);
EXPECT_FALSE(imm()->state()->IsInputMethodAllowed(extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(component_extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(arc_ime_id));
ToggleTabletMode(false);
EXPECT_FALSE(imm()->state()->IsInputMethodAllowed(extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(component_extension_ime_id));
EXPECT_FALSE(imm()->state()->IsInputMethodAllowed(arc_ime_id));
ToggleTabletMode(false);
EXPECT_FALSE(imm()->state()->IsInputMethodAllowed(extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(component_extension_ime_id));
EXPECT_FALSE(imm()->state()->IsInputMethodAllowed(arc_ime_id));
}
TEST_F(ArcInputMethodManagerServiceTest,
DisallowArcIMEsWhenAccessibilityKeyboardEnabled) {
namespace ceiu = chromeos::extension_ime_util;
using crx_file::id_util::GenerateId;
const std::string extension_ime_id =
ceiu::GetInputMethodID(GenerateId("test.extension.ime"), "us");
const std::string component_extension_ime_id =
ceiu::GetComponentInputMethodID(
GenerateId("test.component.extension.ime"), "us");
const std::string arc_ime_id =
ceiu::GetArcInputMethodID(GenerateId("test.arc.ime"), "us");
// Start from tablet mode.
ToggleTabletMode(true);
// Activate 3 IMEs.
imm()->state()->AddActiveInputMethodId(extension_ime_id);
imm()->state()->AddActiveInputMethodId(component_extension_ime_id);
imm()->state()->AddActiveInputMethodId(arc_ime_id);
// Update the prefs because the testee checks them. Note that toggling the
// mode never changes the prefs.
profile()->GetPrefs()->SetString(
prefs::kLanguageEnabledImes,
base::StringPrintf("%s,%s,%s", extension_ime_id.c_str(),
component_extension_ime_id.c_str(),
arc_ime_id.c_str()));
// All IMEs are allowed to use.
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(component_extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(arc_ime_id));
// Enable a11y keyboard option.
profile()->GetPrefs()->SetBoolean(
ash::prefs::kAccessibilityVirtualKeyboardEnabled, true);
// Notify ArcInputMethodManagerService.
service()->OnAccessibilityStatusChanged(
{chromeos::ACCESSIBILITY_TOGGLE_VIRTUAL_KEYBOARD, true});
// ARC IME is not allowed.
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(component_extension_ime_id));
EXPECT_FALSE(imm()->state()->IsInputMethodAllowed(arc_ime_id));
// Disable a11y keyboard option.
profile()->GetPrefs()->SetBoolean(
ash::prefs::kAccessibilityVirtualKeyboardEnabled, false);
// Notify ArcInputMethodManagerService.
service()->OnAccessibilityStatusChanged(
{chromeos::ACCESSIBILITY_TOGGLE_VIRTUAL_KEYBOARD, false});
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(component_extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(arc_ime_id));
}
TEST_F(ArcInputMethodManagerServiceTest,
AllowArcIMEsWhileCommandLineFlagIsSet) {
namespace ceiu = chromeos::extension_ime_util;
using crx_file::id_util::GenerateId;
const std::string extension_ime_id =
ceiu::GetInputMethodID(GenerateId("test.extension.ime"), "us");
const std::string component_extension_ime_id =
ceiu::GetComponentInputMethodID(
GenerateId("test.component.extension.ime"), "us");
const std::string arc_ime_id =
ceiu::GetArcInputMethodID(GenerateId("test.arc.ime"), "us");
// Add '--enable-virtual-keyboard' flag.
base::test::ScopedCommandLine scoped_command_line;
base::CommandLine* command_line = scoped_command_line.GetProcessCommandLine();
command_line->AppendSwitch(keyboard::switches::kEnableVirtualKeyboard);
// Start from tablet mode.
ToggleTabletMode(true);
// Activate 3 IMEs.
imm()->state()->AddActiveInputMethodId(extension_ime_id);
imm()->state()->AddActiveInputMethodId(component_extension_ime_id);
imm()->state()->AddActiveInputMethodId(arc_ime_id);
// All IMEs are allowed to use.
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(component_extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(arc_ime_id));
// Change to laptop mode.
ToggleTabletMode(false);
// All IMEs are allowed to use even in laptop mode if the flag is set.
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(component_extension_ime_id));
EXPECT_TRUE(imm()->state()->IsInputMethodAllowed(arc_ime_id));
}
TEST_F(ArcInputMethodManagerServiceTest, FocusAndBlur) {
ToggleTabletMode(true);
// Adding one ARC IME.
{
const std::string android_ime_id = "test.arc.ime";
const std::string display_name = "DisplayName";
const std::string settings_url = "url_to_settings";
mojom::ImeInfoPtr info = mojom::ImeInfo::New();
info->ime_id = android_ime_id;
info->display_name = display_name;
info->enabled = false;
info->settings_url = settings_url;
std::vector<mojom::ImeInfoPtr> info_array;
info_array.emplace_back(std::move(info));
service()->OnImeInfoChanged(std::move(info_array));
}
// The proxy IME engine should be added.
ASSERT_EQ(1u, imm()->state()->added_input_method_extensions_.size());
ui::IMEEngineHandlerInterface* engine_handler =
std::get<2>(imm()->state()->added_input_method_extensions_.at(0));
// Set up mock input context.
constexpr int test_context_id = 0;
const ui::IMEEngineHandlerInterface::InputContext test_context{
test_context_id,
ui::TEXT_INPUT_TYPE_TEXT,
ui::TEXT_INPUT_MODE_DEFAULT,
0 /* flags */,
ui::TextInputClient::FOCUS_REASON_MOUSE,
true /* should_do_learning */};
ui::MockInputMethod mock_input_method(nullptr);
TestIMEInputContextHandler test_context_handler(&mock_input_method);
ui::DummyTextInputClient dummy_text_input_client(ui::TEXT_INPUT_TYPE_TEXT);
ui::IMEBridge::Get()->SetInputContextHandler(&test_context_handler);
// Enable the ARC IME.
ui::IMEBridge::Get()->SetCurrentEngineHandler(engine_handler);
engine_handler->Enable(
chromeos::extension_ime_util::GetComponentIDByInputMethodID(
std::get<1>(imm()->state()->added_input_method_extensions_.at(0))
.at(0)
.id()));
mock_input_method.SetFocusedTextInputClient(&dummy_text_input_client);
ASSERT_EQ(0, bridge()->focus_calls_count_);
engine_handler->FocusIn(test_context);
EXPECT_EQ(1, bridge()->focus_calls_count_);
engine_handler->FocusOut();
EXPECT_EQ(1, bridge()->focus_calls_count_);
}
TEST_F(ArcInputMethodManagerServiceTest, DisableFallbackVirtualKeyboard) {
namespace ceiu = chromeos::extension_ime_util;
using crx_file::id_util::GenerateId;
ToggleTabletMode(true);
const std::string extension_ime_id =
ceiu::GetInputMethodID(GenerateId("test.extension.ime"), "us");
const std::string component_extension_ime_id =
ceiu::GetComponentInputMethodID(
GenerateId("test.component.extension.ime"), "us");
const std::string arc_ime_id = ceiu::GetArcInputMethodID(
GenerateId("test.arc.ime"), "ime.id.in.arc.container");
// Set active input method to the extension ime.
imm()->state()->SetActiveInputMethod(extension_ime_id);
service()->InputMethodChanged(imm(), profile(), false /* show_message */);
// Enable Chrome OS virtual keyboard
auto* client = ChromeKeyboardControllerClient::Get();
client->ClearEnableFlag(keyboard::KeyboardEnableFlag::kAndroidDisabled);
client->SetEnableFlag(keyboard::KeyboardEnableFlag::kTouchEnabled);
base::RunLoop().RunUntilIdle(); // Allow observers to fire and process.
ASSERT_FALSE(
client->IsEnableFlagSet(keyboard::KeyboardEnableFlag::kAndroidDisabled));
// It's disabled when the ARC IME is activated.
imm()->state()->SetActiveInputMethod(arc_ime_id);
service()->InputMethodChanged(imm(), profile(), false);
EXPECT_TRUE(
client->IsEnableFlagSet(keyboard::KeyboardEnableFlag::kAndroidDisabled));
// It's re-enabled when the ARC IME is deactivated.
imm()->state()->SetActiveInputMethod(component_extension_ime_id);
service()->InputMethodChanged(imm(), profile(), false);
EXPECT_FALSE(
client->IsEnableFlagSet(keyboard::KeyboardEnableFlag::kAndroidDisabled));
}
TEST_F(ArcInputMethodManagerServiceTest, ShowVirtualKeyboard) {
ToggleTabletMode(true);
// Adding one ARC IME.
{
const std::string android_ime_id = "test.arc.ime";
const std::string display_name = "DisplayName";
const std::string settings_url = "url_to_settings";
mojom::ImeInfoPtr info = mojom::ImeInfo::New();
info->ime_id = android_ime_id;
info->display_name = display_name;
info->enabled = false;
info->settings_url = settings_url;
std::vector<mojom::ImeInfoPtr> info_array;
info_array.emplace_back(std::move(info));
service()->OnImeInfoChanged(std::move(info_array));
}
// The proxy IME engine should be added.
ASSERT_EQ(1u, imm()->state()->added_input_method_extensions_.size());
ui::IMEEngineHandlerInterface* engine_handler =
std::get<2>(imm()->state()->added_input_method_extensions_.at(0));
// Set up mock input context.
constexpr int test_context_id = 0;
const ui::IMEEngineHandlerInterface::InputContext test_context{
test_context_id,
ui::TEXT_INPUT_TYPE_TEXT,
ui::TEXT_INPUT_MODE_DEFAULT,
0 /* flags */,
ui::TextInputClient::FOCUS_REASON_MOUSE,
true /* should_do_learning */};
ui::MockInputMethod mock_input_method(nullptr);
TestIMEInputContextHandler test_context_handler(&mock_input_method);
ui::DummyTextInputClient dummy_text_input_client(ui::TEXT_INPUT_TYPE_TEXT);
ui::IMEBridge::Get()->SetInputContextHandler(&test_context_handler);
// Enable the ARC IME.
ui::IMEBridge::Get()->SetCurrentEngineHandler(engine_handler);
engine_handler->Enable(
chromeos::extension_ime_util::GetComponentIDByInputMethodID(
std::get<1>(imm()->state()->added_input_method_extensions_.at(0))
.at(0)
.id()));
mock_input_method.SetFocusedTextInputClient(&dummy_text_input_client);
EXPECT_EQ(0, bridge()->show_virtual_keyboard_calls_count_);
mock_input_method.ShowVirtualKeyboardIfEnabled();
EXPECT_EQ(1, bridge()->show_virtual_keyboard_calls_count_);
ui::IMEBridge::Get()->SetInputContextHandler(nullptr);
ui::IMEBridge::Get()->SetCurrentEngineHandler(nullptr);
}
} // namespace arc