1 // Copyright 2018 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/arc/input_method_manager/arc_input_method_manager_service.h"
6
7 #include <memory>
8 #include <tuple>
9 #include <utility>
10 #include <vector>
11
12 #include "ash/public/cpp/ash_pref_names.h"
13 #include "ash/public/cpp/keyboard/arc/arc_input_method_bounds_tracker.h"
14 #include "ash/public/cpp/keyboard/keyboard_switches.h"
15 #include "ash/public/cpp/tablet_mode.h"
16 #include "base/macros.h"
17 #include "base/memory/ptr_util.h"
18 #include "base/memory/scoped_refptr.h"
19 #include "base/run_loop.h"
20 #include "base/stl_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/test/scoped_command_line.h"
24 #include "chrome/browser/chromeos/arc/input_method_manager/test_input_method_manager_bridge.h"
25 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client_test_helper.h"
26 #include "chrome/common/pref_names.h"
27 #include "chrome/test/base/testing_profile.h"
28 #include "components/arc/arc_service_manager.h"
29 #include "components/arc/test/test_browser_context.h"
30 #include "components/crx_file/id_util.h"
31 #include "content/public/test/browser_task_environment.h"
32 #include "testing/gmock/include/gmock/gmock.h"
33 #include "testing/gtest/include/gtest/gtest.h"
34 #include "ui/base/ime/chromeos/extension_ime_util.h"
35 #include "ui/base/ime/chromeos/ime_bridge.h"
36 #include "ui/base/ime/chromeos/mock_ime_input_context_handler.h"
37 #include "ui/base/ime/chromeos/mock_input_method_manager.h"
38 #include "ui/base/ime/dummy_text_input_client.h"
39 #include "ui/base/ime/mock_input_method.h"
40 #include "ui/views/widget/widget.h"
41
42 namespace arc {
43 namespace {
44
45 namespace im = chromeos::input_method;
46
GenerateImeInfo(const std::string & id,const std::string & name,const std::string & url,bool enabled,bool always_allowed)47 mojom::ImeInfoPtr GenerateImeInfo(const std::string& id,
48 const std::string& name,
49 const std::string& url,
50 bool enabled,
51 bool always_allowed) {
52 mojom::ImeInfoPtr info = mojom::ImeInfo::New();
53 info->ime_id = id;
54 info->display_name = name;
55 info->settings_url = url;
56 info->enabled = enabled;
57 info->is_allowed_in_clamshell_mode = always_allowed;
58 return info;
59 }
60
61 class FakeTabletMode : public ash::TabletMode {
62 public:
63 FakeTabletMode() = default;
64 ~FakeTabletMode() override = default;
65
66 // ash::TabletMode overrides:
AddObserver(ash::TabletModeObserver * observer)67 void AddObserver(ash::TabletModeObserver* observer) override {
68 observer_ = observer;
69 }
70
RemoveObserver(ash::TabletModeObserver * observer)71 void RemoveObserver(ash::TabletModeObserver* observer) override {
72 observer_ = nullptr;
73 }
74
InTabletMode() const75 bool InTabletMode() const override { return in_tablet_mode; }
76
ForceUiTabletModeState(base::Optional<bool> enabled)77 void ForceUiTabletModeState(base::Optional<bool> enabled) override {}
78
SetEnabledForTest(bool enabled)79 void SetEnabledForTest(bool enabled) override {
80 bool changed = (in_tablet_mode != enabled);
81 in_tablet_mode = enabled;
82
83 if (changed && observer_) {
84 if (in_tablet_mode)
85 observer_->OnTabletModeStarted();
86 else
87 observer_->OnTabletModeEnded();
88 }
89 }
90
91 private:
92 ash::TabletModeObserver* observer_ = nullptr;
93 bool in_tablet_mode = false;
94 };
95
96 class FakeInputMethodBoundsObserver
97 : public ArcInputMethodManagerService::Observer {
98 public:
99 FakeInputMethodBoundsObserver() = default;
100 FakeInputMethodBoundsObserver(const FakeInputMethodBoundsObserver&) = delete;
101 ~FakeInputMethodBoundsObserver() override = default;
102
Reset()103 void Reset() {
104 last_visibility_ = false;
105 visibility_changed_call_count_ = 0;
106 }
107
last_visibility() const108 bool last_visibility() const { return last_visibility_; }
109
visibility_changed_call_count() const110 int visibility_changed_call_count() const {
111 return visibility_changed_call_count_;
112 }
113
114 // ArcInputMethodManagerService::Observer:
OnAndroidVirtualKeyboardVisibilityChanged(bool visible)115 void OnAndroidVirtualKeyboardVisibilityChanged(bool visible) override {
116 last_visibility_ = visible;
117 ++visibility_changed_call_count_;
118 }
119
120 private:
121 bool last_visibility_ = false;
122 int visibility_changed_call_count_ = 0;
123 };
124
125 // The fake im::InputMethodManager for testing.
126 class TestInputMethodManager : public im::MockInputMethodManager {
127 public:
128 // The fake im::InputMethodManager::State implementation for testing.
129 class TestState : public im::MockInputMethodManager::State {
130 public:
TestState()131 TestState()
132 : added_input_method_extensions_(), active_input_method_ids_() {}
133
GetActiveInputMethodIds() const134 const std::vector<std::string>& GetActiveInputMethodIds() const override {
135 return active_input_method_ids_;
136 }
137
GetCurrentInputMethod() const138 im::InputMethodDescriptor GetCurrentInputMethod() const override {
139 im::InputMethodDescriptor descriptor(
140 active_ime_id_, "", "", std::vector<std::string>(),
141 std::vector<std::string>(), false /* is_login_keyboard */, GURL(),
142 GURL());
143 return descriptor;
144 }
145
AddInputMethodExtension(const std::string & extension_id,const im::InputMethodDescriptors & descriptors,ui::IMEEngineHandlerInterface * instance)146 void AddInputMethodExtension(
147 const std::string& extension_id,
148 const im::InputMethodDescriptors& descriptors,
149 ui::IMEEngineHandlerInterface* instance) override {
150 added_input_method_extensions_.push_back(
151 std::make_tuple(extension_id, descriptors, instance));
152 }
153
RemoveInputMethodExtension(const std::string & extension_id)154 void RemoveInputMethodExtension(const std::string& extension_id) override {
155 removed_input_method_extensions_.push_back(extension_id);
156 }
157
EnableInputMethod(const std::string & new_active_input_method_id)158 bool EnableInputMethod(
159 const std::string& new_active_input_method_id) override {
160 enabled_input_methods_.push_back(new_active_input_method_id);
161 return true;
162 }
163
AddActiveInputMethodId(const std::string & ime_id)164 void AddActiveInputMethodId(const std::string& ime_id) {
165 if (!std::count(active_input_method_ids_.begin(),
166 active_input_method_ids_.end(), ime_id)) {
167 active_input_method_ids_.push_back(ime_id);
168 }
169 }
170
RemoveActiveInputMethodId(const std::string & ime_id)171 void RemoveActiveInputMethodId(const std::string& ime_id) {
172 base::EraseIf(active_input_method_ids_,
173 [&ime_id](const std::string& id) { return id == ime_id; });
174 }
175
SetActiveInputMethod(const std::string & ime_id)176 void SetActiveInputMethod(const std::string& ime_id) {
177 active_ime_id_ = ime_id;
178 }
179
GetInputMethodExtensions(im::InputMethodDescriptors * descriptors)180 void GetInputMethodExtensions(
181 im::InputMethodDescriptors* descriptors) override {
182 for (const auto& id : active_input_method_ids_) {
183 descriptors->push_back(im::InputMethodDescriptor(
184 id, "", "", {}, {}, false, GURL(), GURL()));
185 }
186 }
187
Reset()188 void Reset() {
189 added_input_method_extensions_.clear();
190 removed_input_method_extensions_.clear();
191 enabled_input_methods_.clear();
192 }
193
194 std::vector<std::tuple<std::string,
195 im::InputMethodDescriptors,
196 ui::IMEEngineHandlerInterface*>>
197 added_input_method_extensions_;
198 std::vector<std::string> removed_input_method_extensions_;
199 std::vector<std::string> enabled_input_methods_;
200
201 protected:
202 friend base::RefCounted<InputMethodManager::State>;
203 ~TestState() override = default;
204
205 private:
206 std::vector<std::string> active_input_method_ids_;
207 std::string active_ime_id_;
208 };
209
TestInputMethodManager()210 TestInputMethodManager() {
211 state_ = scoped_refptr<TestState>(new TestState());
212 }
213 ~TestInputMethodManager() override = default;
214
GetActiveIMEState()215 scoped_refptr<InputMethodManager::State> GetActiveIMEState() override {
216 return state_;
217 }
218
state()219 TestState* state() { return state_.get(); }
220
221 private:
222 scoped_refptr<TestState> state_;
223
224 DISALLOW_COPY_AND_ASSIGN(TestInputMethodManager);
225 };
226
227 class TestIMEInputContextHandler : public ui::MockIMEInputContextHandler {
228 public:
TestIMEInputContextHandler(ui::InputMethod * input_method)229 explicit TestIMEInputContextHandler(ui::InputMethod* input_method)
230 : input_method_(input_method) {}
231
GetInputMethod()232 ui::InputMethod* GetInputMethod() override { return input_method_; }
233
234 private:
235 ui::InputMethod* const input_method_;
236
237 DISALLOW_COPY_AND_ASSIGN(TestIMEInputContextHandler);
238 };
239
240 class ArcInputMethodManagerServiceTest : public testing::Test {
241 protected:
ArcInputMethodManagerServiceTest()242 ArcInputMethodManagerServiceTest()
243 : arc_service_manager_(std::make_unique<ArcServiceManager>()) {}
244 ~ArcInputMethodManagerServiceTest() override = default;
245
service()246 ArcInputMethodManagerService* service() { return service_; }
247
bridge()248 TestInputMethodManagerBridge* bridge() { return test_bridge_; }
249
imm()250 TestInputMethodManager* imm() { return input_method_manager_; }
251
profile()252 TestingProfile* profile() { return profile_.get(); }
253
ToggleTabletMode(bool enabled)254 void ToggleTabletMode(bool enabled) {
255 tablet_mode_controller_->SetEnabledForTest(enabled);
256 }
257
NotifyNewBounds(const gfx::Rect & bounds)258 void NotifyNewBounds(const gfx::Rect& bounds) {
259 input_method_bounds_tracker_->NotifyArcInputMethodBoundsChanged(bounds);
260 }
261
GetEnabledInputMethodIds()262 std::vector<std::string> GetEnabledInputMethodIds() {
263 return base::SplitString(
264 profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes), ",",
265 base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
266 }
267
SetUp()268 void SetUp() override {
269 ui::IMEBridge::Initialize();
270 input_method_manager_ = new TestInputMethodManager();
271 chromeos::input_method::InputMethodManager::Initialize(
272 input_method_manager_);
273 profile_ = std::make_unique<TestingProfile>();
274
275 tablet_mode_controller_ = std::make_unique<FakeTabletMode>();
276 input_method_bounds_tracker_ =
277 std::make_unique<ash::ArcInputMethodBoundsTracker>();
278
279 chrome_keyboard_controller_client_test_helper_ =
280 ChromeKeyboardControllerClientTestHelper::InitializeWithFake();
281 chrome_keyboard_controller_client_test_helper_->SetProfile(profile_.get());
282
283 service_ = ArcInputMethodManagerService::GetForBrowserContextForTesting(
284 profile_.get());
285 test_bridge_ = new TestInputMethodManagerBridge();
286 service_->SetInputMethodManagerBridgeForTesting(
287 base::WrapUnique(test_bridge_));
288 }
289
TearDown()290 void TearDown() override {
291 test_bridge_ = nullptr;
292 service_->Shutdown();
293 chrome_keyboard_controller_client_test_helper_.reset();
294 input_method_bounds_tracker_.reset();
295 tablet_mode_controller_.reset();
296 profile_.reset();
297 chromeos::input_method::InputMethodManager::Shutdown();
298 ui::IMEBridge::Shutdown();
299 }
300
301 private:
302 content::BrowserTaskEnvironment task_environment_;
303
304 std::unique_ptr<ArcServiceManager> arc_service_manager_;
305 std::unique_ptr<TestingProfile> profile_;
306 std::unique_ptr<ChromeKeyboardControllerClientTestHelper>
307 chrome_keyboard_controller_client_test_helper_;
308 std::unique_ptr<FakeTabletMode> tablet_mode_controller_;
309 std::unique_ptr<ash::ArcInputMethodBoundsTracker>
310 input_method_bounds_tracker_;
311 TestInputMethodManager* input_method_manager_ = nullptr;
312 TestInputMethodManagerBridge* test_bridge_ = nullptr; // Owned by |service_|
313 ArcInputMethodManagerService* service_ = nullptr;
314
315 DISALLOW_COPY_AND_ASSIGN(ArcInputMethodManagerServiceTest);
316 };
317
318 } // anonymous namespace
319
TEST_F(ArcInputMethodManagerServiceTest,EnableIme)320 TEST_F(ArcInputMethodManagerServiceTest, EnableIme) {
321 namespace ceiu = chromeos::extension_ime_util;
322 using crx_file::id_util::GenerateId;
323
324 ToggleTabletMode(true);
325
326 ASSERT_EQ(0u, bridge()->enable_ime_calls_.size());
327
328 const std::string extension_ime_id =
329 ceiu::GetInputMethodID(GenerateId("test.extension.ime"), "us");
330 const std::string component_extension_ime_id =
331 ceiu::GetComponentInputMethodID(
332 GenerateId("test.component.extension.ime"), "us");
333 const std::string arc_ime_id =
334 ceiu::GetArcInputMethodID(GenerateId("test.arc.ime"), "us");
335
336 // EnableIme is called only when ARC IME is enable or disabled.
337 imm()->state()->AddActiveInputMethodId(extension_ime_id);
338 service()->ImeMenuListChanged();
339 EXPECT_EQ(0u, bridge()->enable_ime_calls_.size());
340
341 imm()->state()->AddActiveInputMethodId(component_extension_ime_id);
342 service()->ImeMenuListChanged();
343 EXPECT_EQ(0u, bridge()->enable_ime_calls_.size());
344
345 // Enable the ARC IME and verify that EnableIme is called.
346 imm()->state()->AddActiveInputMethodId(arc_ime_id);
347 service()->ImeMenuListChanged();
348 ASSERT_EQ(1u, bridge()->enable_ime_calls_.size());
349 EXPECT_EQ(ceiu::GetComponentIDByInputMethodID(arc_ime_id),
350 std::get<std::string>(bridge()->enable_ime_calls_[0]));
351 EXPECT_TRUE(std::get<bool>(bridge()->enable_ime_calls_[0]));
352
353 // Disable the ARC IME and verify that EnableIme is called with false.
354 imm()->state()->RemoveActiveInputMethodId(arc_ime_id);
355 service()->ImeMenuListChanged();
356 ASSERT_EQ(2u, bridge()->enable_ime_calls_.size());
357 EXPECT_EQ(ceiu::GetComponentIDByInputMethodID(arc_ime_id),
358 std::get<std::string>(bridge()->enable_ime_calls_[1]));
359 EXPECT_FALSE(std::get<bool>(bridge()->enable_ime_calls_[1]));
360
361 // EnableIme is not called when non ARC IME is disabled.
362 imm()->state()->RemoveActiveInputMethodId(extension_ime_id);
363 service()->ImeMenuListChanged();
364 EXPECT_EQ(2u, bridge()->enable_ime_calls_.size());
365 }
366
TEST_F(ArcInputMethodManagerServiceTest,EnableIme_WithPrefs)367 TEST_F(ArcInputMethodManagerServiceTest, EnableIme_WithPrefs) {
368 namespace ceiu = chromeos::extension_ime_util;
369 using crx_file::id_util::GenerateId;
370
371 ToggleTabletMode(true);
372
373 ASSERT_EQ(0u, bridge()->enable_ime_calls_.size());
374
375 const std::string component_extension_ime_id =
376 ceiu::GetComponentInputMethodID(
377 GenerateId("test.component.extension.ime"), "us");
378 const std::string arc_ime_id =
379 ceiu::GetArcInputMethodID(GenerateId("test.arc.ime"), "us");
380
381 imm()->state()->AddActiveInputMethodId(component_extension_ime_id);
382 service()->ImeMenuListChanged();
383 EXPECT_EQ(0u, bridge()->enable_ime_calls_.size());
384
385 imm()->state()->AddActiveInputMethodId(arc_ime_id);
386 service()->ImeMenuListChanged();
387 ASSERT_EQ(1u, bridge()->enable_ime_calls_.size());
388
389 // Test the case where |arc_ime_id| is temporarily disallowed because of the
390 // toggling to the laptop mode. In that case, the prefs still have the IME's
391 // ID.
392 profile()->GetPrefs()->SetString(
393 prefs::kLanguageEnabledImes,
394 base::StringPrintf("%s,%s", component_extension_ime_id.c_str(),
395 arc_ime_id.c_str()));
396 imm()->state()->RemoveActiveInputMethodId(arc_ime_id);
397 service()->ImeMenuListChanged();
398 // Verify that EnableIme(id, false) is NOT called.
399 EXPECT_EQ(1u, bridge()->enable_ime_calls_.size()); // still 1u, not 2u.
400 }
401
TEST_F(ArcInputMethodManagerServiceTest,SwitchImeTo)402 TEST_F(ArcInputMethodManagerServiceTest, SwitchImeTo) {
403 namespace ceiu = chromeos::extension_ime_util;
404 using crx_file::id_util::GenerateId;
405
406 const std::string arc_ime_service_id =
407 "org.chromium.arc.ime/.ArcInputMethodService";
408
409 ToggleTabletMode(true);
410
411 ASSERT_EQ(0u, bridge()->switch_ime_to_calls_.size());
412
413 const std::string extension_ime_id =
414 ceiu::GetInputMethodID(GenerateId("test.extension.ime"), "us");
415 const std::string component_extension_ime_id =
416 ceiu::GetComponentInputMethodID(
417 GenerateId("test.component.extension.ime"), "us");
418 const std::string arc_ime_id = ceiu::GetArcInputMethodID(
419 GenerateId("test.arc.ime"), "ime.id.in.arc.container");
420
421 // Set active input method to the extension ime.
422 imm()->state()->SetActiveInputMethod(extension_ime_id);
423 service()->InputMethodChanged(imm(), nullptr, false /* show_message */);
424 // ArcImeService should be selected.
425 ASSERT_EQ(1u, bridge()->switch_ime_to_calls_.size());
426 EXPECT_EQ(arc_ime_service_id, bridge()->switch_ime_to_calls_[0]);
427
428 // Set active input method to the component extension ime.
429 imm()->state()->SetActiveInputMethod(component_extension_ime_id);
430 service()->InputMethodChanged(imm(), nullptr, false /* show_message */);
431 // ArcImeService should be selected.
432 ASSERT_EQ(2u, bridge()->switch_ime_to_calls_.size());
433 EXPECT_EQ(arc_ime_service_id, bridge()->switch_ime_to_calls_[1]);
434
435 // Set active input method to the arc ime.
436 imm()->state()->SetActiveInputMethod(arc_ime_id);
437 service()->InputMethodChanged(imm(), nullptr, false /* show_message */);
438 ASSERT_EQ(3u, bridge()->switch_ime_to_calls_.size());
439 EXPECT_EQ("ime.id.in.arc.container", bridge()->switch_ime_to_calls_[2]);
440 }
441
TEST_F(ArcInputMethodManagerServiceTest,OnImeDisabled)442 TEST_F(ArcInputMethodManagerServiceTest, OnImeDisabled) {
443 namespace ceiu = chromeos::extension_ime_util;
444
445 constexpr char kNonArcIme[] = "ime_a";
446 constexpr char kArcImeX[] = "arc_ime_x";
447 constexpr char kArcImeY[] = "arc_ime_y";
448 constexpr char kArcIMEProxyExtensionName[] =
449 "org.chromium.arc.inputmethod.proxy";
450
451 const std::string proxy_ime_extension_id =
452 crx_file::id_util::GenerateId(kArcIMEProxyExtensionName);
453 const std::string arc_ime_x_component =
454 ceiu::GetArcInputMethodID(proxy_ime_extension_id, kArcImeX);
455 const std::string arc_ime_y_component =
456 ceiu::GetArcInputMethodID(proxy_ime_extension_id, kArcImeY);
457 mojom::ImeInfoPtr arc_ime_x = GenerateImeInfo(kArcImeX, "", "", false, false);
458 mojom::ImeInfoPtr arc_ime_y = GenerateImeInfo(kArcImeY, "", "", false, false);
459
460 ToggleTabletMode(true);
461
462 // Adding two ARC IMEs.
463 {
464 std::vector<mojom::ImeInfoPtr> info_array;
465 info_array.emplace_back(arc_ime_x.Clone());
466 info_array.emplace_back(arc_ime_y.Clone());
467 service()->OnImeInfoChanged(std::move(info_array));
468 }
469
470 // Enable one non-ARC IME, then remove an ARC IME. This usually does not
471 // happen, but confirm that OnImeDisabled() does not do anything bad even
472 // if the IPC is called that way.
473 profile()->GetPrefs()->SetString(prefs::kLanguageEnabledImes, kNonArcIme);
474 service()->OnImeDisabled(kArcImeX);
475 EXPECT_EQ(kNonArcIme,
476 profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes));
477
478 // Enable two IMEs (one non-ARC and one ARC), remove the ARC IME, and then
479 // confirm the non-ARC one remains.
480 arc_ime_x->enabled = true;
481 {
482 std::vector<mojom::ImeInfoPtr> info_array;
483 info_array.emplace_back(arc_ime_x.Clone());
484 info_array.emplace_back(arc_ime_y.Clone());
485 service()->OnImeInfoChanged(std::move(info_array));
486 }
487 std::string pref_str =
488 base::StringPrintf("%s,%s", kNonArcIme, arc_ime_x_component.c_str());
489 EXPECT_EQ(pref_str,
490 profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes));
491 service()->OnImeDisabled(kArcImeX);
492 EXPECT_EQ(kNonArcIme,
493 profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes));
494
495 // Enable two ARC IMEs along with one non-ARC one, remove one of two ARC IMEs,
496 // then confirm one non-ARC IME and one ARC IME still remain.
497 arc_ime_y->enabled = true;
498 {
499 std::vector<mojom::ImeInfoPtr> info_array;
500 info_array.emplace_back(arc_ime_x.Clone());
501 info_array.emplace_back(arc_ime_y.Clone());
502 service()->OnImeInfoChanged(std::move(info_array));
503 }
504 pref_str =
505 base::StringPrintf("%s,%s,%s", kNonArcIme, arc_ime_x_component.c_str(),
506 arc_ime_y_component.c_str());
507 EXPECT_EQ(pref_str,
508 profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes));
509 service()->OnImeDisabled(kArcImeX);
510 pref_str =
511 base::StringPrintf("%s,%s", kNonArcIme, arc_ime_y_component.c_str());
512 EXPECT_EQ(pref_str,
513 profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes));
514 }
515
TEST_F(ArcInputMethodManagerServiceTest,OnImeInfoChanged)516 TEST_F(ArcInputMethodManagerServiceTest, OnImeInfoChanged) {
517 namespace ceiu = chromeos::extension_ime_util;
518
519 ToggleTabletMode(true);
520
521 // Preparing 2 ImeInfo.
522 const std::string android_ime_id1 = "test.arc.ime";
523 const std::string display_name1 = "DisplayName";
524 const std::string settings_url1 = "url_to_settings";
525 mojom::ImeInfoPtr info1 = GenerateImeInfo(android_ime_id1, display_name1,
526 settings_url1, false, false);
527
528 const std::string android_ime_id2 = "test.arc.ime2";
529 const std::string display_name2 = "DisplayName2";
530 const std::string settings_url2 = "url_to_settings2";
531 mojom::ImeInfoPtr info2 = GenerateImeInfo(android_ime_id2, display_name2,
532 settings_url2, true, false);
533
534 std::vector<
535 std::tuple<std::string, chromeos::input_method::InputMethodDescriptors,
536 ui::IMEEngineHandlerInterface*>>& added_extensions =
537 imm()->state()->added_input_method_extensions_;
538 ASSERT_EQ(0u, added_extensions.size());
539
540 {
541 // Passing empty info_array shouldn't call AddInputMethodExtension.
542 std::vector<mojom::ImeInfoPtr> info_array{};
543 service()->OnImeInfoChanged(std::move(info_array));
544 EXPECT_TRUE(added_extensions.empty());
545 }
546
547 {
548 // Adding one ARC IME.
549 std::vector<mojom::ImeInfoPtr> info_array;
550 info_array.emplace_back(info1.Clone());
551 service()->OnImeInfoChanged(std::move(info_array));
552 ASSERT_EQ(1u, added_extensions.size());
553 ASSERT_EQ(1u, std::get<1>(added_extensions[0]).size());
554 EXPECT_EQ(android_ime_id1, ceiu::GetComponentIDByInputMethodID(
555 std::get<1>(added_extensions[0])[0].id()));
556 EXPECT_EQ(display_name1, std::get<1>(added_extensions[0])[0].name());
557 ASSERT_EQ(1u, std::get<1>(added_extensions[0])[0].language_codes().size());
558 EXPECT_TRUE(chromeos::extension_ime_util::IsArcIME(
559 std::get<1>(added_extensions[0])[0].id()));
560
561 // Emulate enabling ARC IME from chrome://settings.
562 const std::string& arc_ime_id = std::get<1>(added_extensions[0])[0].id();
563 profile()->GetPrefs()->SetString(prefs::kLanguageEnabledImes, arc_ime_id);
564 EXPECT_EQ(arc_ime_id,
565 profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes));
566
567 // Removing the ARC IME should clear the pref
568 std::vector<mojom::ImeInfoPtr> empty_info_array;
569 service()->OnImeInfoChanged(std::move(empty_info_array));
570 EXPECT_TRUE(
571 profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes).empty());
572 added_extensions.clear();
573 }
574
575 {
576 // Adding two ARC IMEs. One is already enabled.
577 std::vector<mojom::ImeInfoPtr> info_array;
578 info_array.emplace_back(info1.Clone());
579 info_array.emplace_back(info2.Clone());
580 service()->OnImeInfoChanged(std::move(info_array));
581 // The ARC IMEs should be registered as two IMEs in one extension.
582 ASSERT_EQ(1u, added_extensions.size());
583 ASSERT_EQ(2u, std::get<1>(added_extensions[0]).size());
584 EXPECT_EQ(android_ime_id1, ceiu::GetComponentIDByInputMethodID(
585 std::get<1>(added_extensions[0])[0].id()));
586 EXPECT_EQ(display_name1, std::get<1>(added_extensions[0])[0].name());
587 EXPECT_EQ(android_ime_id2, ceiu::GetComponentIDByInputMethodID(
588 std::get<1>(added_extensions[0])[1].id()));
589 EXPECT_EQ(display_name2, std::get<1>(added_extensions[0])[1].name());
590
591 // Already enabled IME should be added to the pref automatically.
592 const std::string& arc_ime_id2 = std::get<1>(added_extensions[0])[1].id();
593 EXPECT_EQ(arc_ime_id2,
594 profile()->GetPrefs()->GetString(prefs::kLanguageEnabledImes));
595
596 added_extensions.clear();
597 }
598 }
599
TEST_F(ArcInputMethodManagerServiceTest,EnableArcIMEsOnlyInTabletMode)600 TEST_F(ArcInputMethodManagerServiceTest, EnableArcIMEsOnlyInTabletMode) {
601 namespace ceiu = chromeos::extension_ime_util;
602 using crx_file::id_util::GenerateId;
603
604 constexpr char kArcIMEProxyExtensionName[] =
605 "org.chromium.arc.inputmethod.proxy";
606
607 const std::string extension_ime_id =
608 ceiu::GetInputMethodID(GenerateId("test.extension.ime"), "us");
609 const std::string component_extension_ime_id =
610 ceiu::GetComponentInputMethodID(
611 GenerateId("test.component.extension.ime"), "us");
612 const std::string proxy_ime_extension_id =
613 crx_file::id_util::GenerateId(kArcIMEProxyExtensionName);
614 const std::string android_ime_id = "test.arc.ime";
615 const std::string arc_ime_id =
616 ceiu::GetArcInputMethodID(proxy_ime_extension_id, android_ime_id);
617
618 // Start from tablet mode.
619 ToggleTabletMode(true);
620
621 // Activate the extension IME and the component extension IME.
622 imm()->state()->AddActiveInputMethodId(extension_ime_id);
623 imm()->state()->AddActiveInputMethodId(component_extension_ime_id);
624 // Update the prefs because the testee checks them.
625 profile()->GetPrefs()->SetString(
626 prefs::kLanguageEnabledImes,
627 base::StringPrintf("%s,%s", extension_ime_id.c_str(),
628 component_extension_ime_id.c_str()));
629 service()->ImeMenuListChanged();
630
631 imm()->state()->Reset();
632
633 // Enable the ARC IME.
634 {
635 mojom::ImeInfoPtr info =
636 GenerateImeInfo(android_ime_id, "", "", true, false);
637 std::vector<mojom::ImeInfoPtr> info_array{};
638 info_array.emplace_back(info.Clone());
639 service()->OnImeInfoChanged(std::move(info_array));
640 }
641 // IMM should get the newly enabled IME id.
642 EXPECT_EQ(1u, imm()->state()->added_input_method_extensions_.size());
643 EXPECT_EQ(1u, imm()->state()->enabled_input_methods_.size());
644 EXPECT_EQ(arc_ime_id, imm()->state()->enabled_input_methods_.at(0));
645 imm()->state()->enabled_input_methods_.clear();
646 {
647 // Pref should get updated.
648 const auto enabled_ime_in_pref = GetEnabledInputMethodIds();
649 EXPECT_EQ(3u, enabled_ime_in_pref.size());
650 EXPECT_EQ(arc_ime_id, enabled_ime_in_pref.at(2));
651 }
652
653 imm()->state()->Reset();
654
655 // Change to laptop mode.
656 ToggleTabletMode(false);
657
658 // ARC IME is not allowed in laptop mode.
659 // The fake IME extension is uninstalled.
660 EXPECT_EQ(1u, imm()->state()->removed_input_method_extensions_.size());
661 EXPECT_TRUE(imm()->state()->enabled_input_methods_.empty());
662 {
663 const auto enabled_ime_in_pref = GetEnabledInputMethodIds();
664 EXPECT_EQ(2u, enabled_ime_in_pref.size());
665 }
666
667 imm()->state()->Reset();
668
669 // Back to tablet mode.
670 ToggleTabletMode(true);
671
672 // All IMEs are allowed to use.
673 EXPECT_EQ(1u, imm()->state()->added_input_method_extensions_.size());
674 EXPECT_EQ(1u, imm()->state()->enabled_input_methods_.size());
675 EXPECT_EQ(arc_ime_id, imm()->state()->enabled_input_methods_.at(0));
676 imm()->state()->enabled_input_methods_.clear();
677 {
678 const auto enabled_ime_in_pref = GetEnabledInputMethodIds();
679 EXPECT_EQ(3u, enabled_ime_in_pref.size());
680 EXPECT_EQ(arc_ime_id, enabled_ime_in_pref.at(2));
681 }
682
683 imm()->state()->Reset();
684
685 // Confirm that entering the same mode twice in a row is no-op.
686 ToggleTabletMode(true);
687 EXPECT_TRUE(imm()->state()->removed_input_method_extensions_.empty());
688 EXPECT_TRUE(imm()->state()->added_input_method_extensions_.empty());
689 EXPECT_TRUE(imm()->state()->enabled_input_methods_.empty());
690
691 ToggleTabletMode(false);
692 EXPECT_EQ(1u, imm()->state()->removed_input_method_extensions_.size());
693 EXPECT_TRUE(imm()->state()->enabled_input_methods_.empty());
694 {
695 const auto enabled_ime_in_pref = GetEnabledInputMethodIds();
696 EXPECT_EQ(2u, enabled_ime_in_pref.size());
697 }
698
699 ToggleTabletMode(false);
700 EXPECT_EQ(1u, imm()->state()->removed_input_method_extensions_.size());
701 EXPECT_TRUE(imm()->state()->enabled_input_methods_.empty());
702 {
703 const auto enabled_ime_in_pref = GetEnabledInputMethodIds();
704 EXPECT_EQ(2u, enabled_ime_in_pref.size());
705 }
706 }
707
TEST_F(ArcInputMethodManagerServiceTest,RemoveArcIMEsWhenAccessibilityKeyboardEnabled)708 TEST_F(ArcInputMethodManagerServiceTest,
709 RemoveArcIMEsWhenAccessibilityKeyboardEnabled) {
710 namespace ceiu = chromeos::extension_ime_util;
711 using crx_file::id_util::GenerateId;
712
713 constexpr char kArcIMEProxyExtensionName[] =
714 "org.chromium.arc.inputmethod.proxy";
715
716 const std::string extension_ime_id =
717 ceiu::GetInputMethodID(GenerateId("test.extension.ime"), "us");
718 const std::string component_extension_ime_id =
719 ceiu::GetComponentInputMethodID(
720 GenerateId("test.component.extension.ime"), "us");
721 const std::string proxy_ime_extension_id =
722 crx_file::id_util::GenerateId(kArcIMEProxyExtensionName);
723 const std::string android_ime_id = "test.arc.ime";
724 const std::string arc_ime_id =
725 ceiu::GetArcInputMethodID(proxy_ime_extension_id, android_ime_id);
726
727 // Start from tablet mode.
728 ToggleTabletMode(true);
729
730 // Activate the extension IME and the component extension IME.
731 imm()->state()->AddActiveInputMethodId(extension_ime_id);
732 imm()->state()->AddActiveInputMethodId(component_extension_ime_id);
733 // Update the prefs because the testee checks them.
734 profile()->GetPrefs()->SetString(
735 prefs::kLanguageEnabledImes,
736 base::StringPrintf("%s,%s", extension_ime_id.c_str(),
737 component_extension_ime_id.c_str()));
738 service()->ImeMenuListChanged();
739
740 imm()->state()->Reset();
741
742 // All IMEs are allowed to use.
743 // Enable the ARC IME.
744 {
745 mojom::ImeInfoPtr info =
746 GenerateImeInfo(android_ime_id, "", "", true, false);
747 std::vector<mojom::ImeInfoPtr> info_array{};
748 info_array.emplace_back(info.Clone());
749 service()->OnImeInfoChanged(std::move(info_array));
750 }
751 // IMM should get the newly enabled IME id.
752 EXPECT_EQ(1u, imm()->state()->added_input_method_extensions_.size());
753 EXPECT_EQ(1u, imm()->state()->enabled_input_methods_.size());
754 EXPECT_EQ(arc_ime_id, imm()->state()->enabled_input_methods_.at(0));
755 imm()->state()->enabled_input_methods_.clear();
756 {
757 // Pref should get updated.
758 const auto enabled_ime_in_pref = GetEnabledInputMethodIds();
759 EXPECT_EQ(3u, enabled_ime_in_pref.size());
760 EXPECT_EQ(arc_ime_id, enabled_ime_in_pref.at(2));
761 }
762
763 imm()->state()->Reset();
764
765 // Enable a11y keyboard option.
766 profile()->GetPrefs()->SetBoolean(
767 ash::prefs::kAccessibilityVirtualKeyboardEnabled, true);
768 // Notify ArcInputMethodManagerService.
769 service()->OnAccessibilityStatusChanged(
770 {chromeos::ACCESSIBILITY_TOGGLE_VIRTUAL_KEYBOARD, true});
771
772 // ARC IME is not allowed when a11y keyboard is enabled.
773 EXPECT_EQ(1u, imm()->state()->removed_input_method_extensions_.size());
774 EXPECT_TRUE(imm()->state()->enabled_input_methods_.empty());
775 {
776 const auto enabled_ime_in_pref = GetEnabledInputMethodIds();
777 EXPECT_EQ(2u, enabled_ime_in_pref.size());
778 }
779
780 imm()->state()->removed_input_method_extensions_.clear();
781 imm()->state()->added_input_method_extensions_.clear();
782 imm()->state()->enabled_input_methods_.clear();
783
784 // Disable a11y keyboard option.
785 profile()->GetPrefs()->SetBoolean(
786 ash::prefs::kAccessibilityVirtualKeyboardEnabled, false);
787 // Notify ArcInputMethodManagerService.
788 service()->OnAccessibilityStatusChanged(
789 {chromeos::ACCESSIBILITY_TOGGLE_VIRTUAL_KEYBOARD, false});
790
791 // ARC IME can be enabled.
792 EXPECT_EQ(1u, imm()->state()->added_input_method_extensions_.size());
793 EXPECT_EQ(1u, imm()->state()->enabled_input_methods_.size());
794 EXPECT_EQ(arc_ime_id, imm()->state()->enabled_input_methods_.at(0));
795 imm()->state()->enabled_input_methods_.clear();
796 {
797 // Pref should get updated.
798 const auto enabled_ime_in_pref = GetEnabledInputMethodIds();
799 EXPECT_EQ(3u, enabled_ime_in_pref.size());
800 EXPECT_EQ(arc_ime_id, enabled_ime_in_pref.at(2));
801 }
802 }
803
TEST_F(ArcInputMethodManagerServiceTest,AllowArcIMEsWhileCommandLineFlagIsSet)804 TEST_F(ArcInputMethodManagerServiceTest,
805 AllowArcIMEsWhileCommandLineFlagIsSet) {
806 namespace ceiu = chromeos::extension_ime_util;
807 using crx_file::id_util::GenerateId;
808
809 constexpr char kArcIMEProxyExtensionName[] =
810 "org.chromium.arc.inputmethod.proxy";
811
812 const std::string extension_ime_id =
813 ceiu::GetInputMethodID(GenerateId("test.extension.ime"), "us");
814 const std::string component_extension_ime_id =
815 ceiu::GetComponentInputMethodID(
816 GenerateId("test.component.extension.ime"), "us");
817 const std::string proxy_ime_extension_id =
818 crx_file::id_util::GenerateId(kArcIMEProxyExtensionName);
819 const std::string android_ime_id = "test.arc.ime";
820 const std::string arc_ime_id =
821 ceiu::GetArcInputMethodID(proxy_ime_extension_id, android_ime_id);
822
823 // Add '--enable-virtual-keyboard' flag.
824 base::test::ScopedCommandLine scoped_command_line;
825 base::CommandLine* command_line = scoped_command_line.GetProcessCommandLine();
826 command_line->AppendSwitch(keyboard::switches::kEnableVirtualKeyboard);
827
828 // Start from tablet mode.
829 ToggleTabletMode(true);
830
831 // Activate the extension IME and the component extension IME.
832 imm()->state()->AddActiveInputMethodId(extension_ime_id);
833 imm()->state()->AddActiveInputMethodId(component_extension_ime_id);
834 // Update the prefs because the testee checks them.
835 profile()->GetPrefs()->SetString(
836 prefs::kLanguageEnabledImes,
837 base::StringPrintf("%s,%s", extension_ime_id.c_str(),
838 component_extension_ime_id.c_str()));
839 service()->ImeMenuListChanged();
840
841 imm()->state()->removed_input_method_extensions_.clear();
842 imm()->state()->added_input_method_extensions_.clear();
843 imm()->state()->enabled_input_methods_.clear();
844
845 // Enable the ARC IME.
846 {
847 mojom::ImeInfoPtr info =
848 GenerateImeInfo(android_ime_id, "", "", true, false);
849 std::vector<mojom::ImeInfoPtr> info_array{};
850 info_array.emplace_back(info.Clone());
851 service()->OnImeInfoChanged(std::move(info_array));
852 }
853 // IMM should get the newly enabled IME id.
854 EXPECT_EQ(1u, imm()->state()->added_input_method_extensions_.size());
855 EXPECT_EQ(1u, imm()->state()->enabled_input_methods_.size());
856 EXPECT_EQ(arc_ime_id, imm()->state()->enabled_input_methods_.at(0));
857 {
858 // Pref should get updated.
859 const auto enabled_ime_in_pref = GetEnabledInputMethodIds();
860 EXPECT_EQ(3u, enabled_ime_in_pref.size());
861 EXPECT_EQ(arc_ime_id, enabled_ime_in_pref.at(2));
862 }
863
864 imm()->state()->removed_input_method_extensions_.clear();
865 imm()->state()->added_input_method_extensions_.clear();
866 imm()->state()->enabled_input_methods_.clear();
867
868 // Change to laptop mode.
869 ToggleTabletMode(false);
870
871 // All IMEs are allowed to use even in laptop mode if the flag is set.
872 EXPECT_EQ(1u, imm()->state()->added_input_method_extensions_.size());
873 EXPECT_EQ(1u, imm()->state()->enabled_input_methods_.size());
874 EXPECT_EQ(arc_ime_id, imm()->state()->enabled_input_methods_.at(0));
875 {
876 const auto enabled_ime_in_pref = GetEnabledInputMethodIds();
877 EXPECT_EQ(3u, enabled_ime_in_pref.size());
878 EXPECT_EQ(arc_ime_id, enabled_ime_in_pref.at(2));
879 }
880 }
881
TEST_F(ArcInputMethodManagerServiceTest,FocusAndBlur)882 TEST_F(ArcInputMethodManagerServiceTest, FocusAndBlur) {
883 ToggleTabletMode(true);
884
885 // Adding one ARC IME.
886 {
887 const std::string android_ime_id = "test.arc.ime";
888 const std::string display_name = "DisplayName";
889 const std::string settings_url = "url_to_settings";
890 mojom::ImeInfoPtr info = GenerateImeInfo(android_ime_id, display_name,
891 settings_url, false, false);
892
893 std::vector<mojom::ImeInfoPtr> info_array;
894 info_array.emplace_back(std::move(info));
895 service()->OnImeInfoChanged(std::move(info_array));
896 }
897 // The proxy IME engine should be added.
898 ASSERT_EQ(1u, imm()->state()->added_input_method_extensions_.size());
899 ui::IMEEngineHandlerInterface* engine_handler =
900 std::get<2>(imm()->state()->added_input_method_extensions_.at(0));
901
902 // Set up mock input context.
903 constexpr int test_context_id = 0;
904 const ui::IMEEngineHandlerInterface::InputContext test_context{
905 test_context_id,
906 ui::TEXT_INPUT_TYPE_TEXT,
907 ui::TEXT_INPUT_MODE_DEFAULT,
908 0 /* flags */,
909 ui::TextInputClient::FOCUS_REASON_MOUSE,
910 true /* should_do_learning */};
911 ui::MockInputMethod mock_input_method(nullptr);
912 TestIMEInputContextHandler test_context_handler(&mock_input_method);
913 ui::DummyTextInputClient dummy_text_input_client(ui::TEXT_INPUT_TYPE_TEXT);
914 ui::IMEBridge::Get()->SetInputContextHandler(&test_context_handler);
915
916 // Enable the ARC IME.
917 ui::IMEBridge::Get()->SetCurrentEngineHandler(engine_handler);
918 engine_handler->Enable(
919 chromeos::extension_ime_util::GetComponentIDByInputMethodID(
920 std::get<1>(imm()->state()->added_input_method_extensions_.at(0))
921 .at(0)
922 .id()));
923 mock_input_method.SetFocusedTextInputClient(&dummy_text_input_client);
924
925 ASSERT_EQ(0, bridge()->focus_calls_count_);
926
927 engine_handler->FocusIn(test_context);
928 EXPECT_EQ(1, bridge()->focus_calls_count_);
929
930 engine_handler->FocusOut();
931 EXPECT_EQ(1, bridge()->focus_calls_count_);
932 }
933
TEST_F(ArcInputMethodManagerServiceTest,DisableFallbackVirtualKeyboard)934 TEST_F(ArcInputMethodManagerServiceTest, DisableFallbackVirtualKeyboard) {
935 namespace ceiu = chromeos::extension_ime_util;
936 using crx_file::id_util::GenerateId;
937
938 ToggleTabletMode(true);
939
940 const std::string extension_ime_id =
941 ceiu::GetInputMethodID(GenerateId("test.extension.ime"), "us");
942 const std::string component_extension_ime_id =
943 ceiu::GetComponentInputMethodID(
944 GenerateId("test.component.extension.ime"), "us");
945 const std::string arc_ime_id = ceiu::GetArcInputMethodID(
946 GenerateId("test.arc.ime"), "ime.id.in.arc.container");
947
948 // Set active input method to the extension ime.
949 imm()->state()->SetActiveInputMethod(extension_ime_id);
950 service()->InputMethodChanged(imm(), profile(), false /* show_message */);
951
952 // Enable Chrome OS virtual keyboard
953 auto* client = ChromeKeyboardControllerClient::Get();
954 client->ClearEnableFlag(keyboard::KeyboardEnableFlag::kAndroidDisabled);
955 client->SetEnableFlag(keyboard::KeyboardEnableFlag::kTouchEnabled);
956 base::RunLoop().RunUntilIdle(); // Allow observers to fire and process.
957 ASSERT_FALSE(
958 client->IsEnableFlagSet(keyboard::KeyboardEnableFlag::kAndroidDisabled));
959
960 // It's disabled when the ARC IME is activated.
961 imm()->state()->SetActiveInputMethod(arc_ime_id);
962 service()->InputMethodChanged(imm(), profile(), false);
963 EXPECT_TRUE(
964 client->IsEnableFlagSet(keyboard::KeyboardEnableFlag::kAndroidDisabled));
965
966 // It's re-enabled when the ARC IME is deactivated.
967 imm()->state()->SetActiveInputMethod(component_extension_ime_id);
968 service()->InputMethodChanged(imm(), profile(), false);
969 EXPECT_FALSE(
970 client->IsEnableFlagSet(keyboard::KeyboardEnableFlag::kAndroidDisabled));
971 }
972
TEST_F(ArcInputMethodManagerServiceTest,ShowVirtualKeyboard)973 TEST_F(ArcInputMethodManagerServiceTest, ShowVirtualKeyboard) {
974 ToggleTabletMode(true);
975
976 // Adding one ARC IME.
977 {
978 const std::string android_ime_id = "test.arc.ime";
979 const std::string display_name = "DisplayName";
980 const std::string settings_url = "url_to_settings";
981 mojom::ImeInfoPtr info = GenerateImeInfo(android_ime_id, display_name,
982 settings_url, false, false);
983
984 std::vector<mojom::ImeInfoPtr> info_array;
985 info_array.emplace_back(std::move(info));
986 service()->OnImeInfoChanged(std::move(info_array));
987 }
988 // The proxy IME engine should be added.
989 ASSERT_EQ(1u, imm()->state()->added_input_method_extensions_.size());
990 ui::IMEEngineHandlerInterface* engine_handler =
991 std::get<2>(imm()->state()->added_input_method_extensions_.at(0));
992
993 // Set up mock input context.
994 constexpr int test_context_id = 0;
995 const ui::IMEEngineHandlerInterface::InputContext test_context{
996 test_context_id,
997 ui::TEXT_INPUT_TYPE_TEXT,
998 ui::TEXT_INPUT_MODE_DEFAULT,
999 0 /* flags */,
1000 ui::TextInputClient::FOCUS_REASON_MOUSE,
1001 true /* should_do_learning */};
1002 ui::MockInputMethod mock_input_method(nullptr);
1003 TestIMEInputContextHandler test_context_handler(&mock_input_method);
1004 ui::DummyTextInputClient dummy_text_input_client(ui::TEXT_INPUT_TYPE_TEXT);
1005 ui::IMEBridge::Get()->SetInputContextHandler(&test_context_handler);
1006
1007 // Enable the ARC IME.
1008 ui::IMEBridge::Get()->SetCurrentEngineHandler(engine_handler);
1009 engine_handler->Enable(
1010 chromeos::extension_ime_util::GetComponentIDByInputMethodID(
1011 std::get<1>(imm()->state()->added_input_method_extensions_.at(0))
1012 .at(0)
1013 .id()));
1014
1015 mock_input_method.SetFocusedTextInputClient(&dummy_text_input_client);
1016
1017 EXPECT_EQ(0, bridge()->show_virtual_keyboard_calls_count_);
1018 mock_input_method.ShowVirtualKeyboardIfEnabled();
1019 EXPECT_EQ(1, bridge()->show_virtual_keyboard_calls_count_);
1020 ui::IMEBridge::Get()->SetInputContextHandler(nullptr);
1021 ui::IMEBridge::Get()->SetCurrentEngineHandler(nullptr);
1022 }
1023
TEST_F(ArcInputMethodManagerServiceTest,VisibilityObserver)1024 TEST_F(ArcInputMethodManagerServiceTest, VisibilityObserver) {
1025 ToggleTabletMode(true);
1026
1027 FakeInputMethodBoundsObserver observer;
1028 service()->AddObserver(&observer);
1029 ASSERT_FALSE(observer.last_visibility());
1030 ASSERT_EQ(0, observer.visibility_changed_call_count());
1031
1032 // Notify new non-empty bounds not when ARC IME is active.
1033 NotifyNewBounds(gfx::Rect(0, 0, 100, 100));
1034 // It should not cause visibility changed event.
1035 EXPECT_FALSE(observer.last_visibility());
1036 EXPECT_EQ(0, observer.visibility_changed_call_count());
1037
1038 NotifyNewBounds(gfx::Rect(0, 0, 0, 0));
1039 EXPECT_FALSE(observer.last_visibility());
1040 EXPECT_EQ(0, observer.visibility_changed_call_count());
1041 observer.Reset();
1042
1043 // Adding one ARC IME.
1044 {
1045 const std::string android_ime_id = "test.arc.ime";
1046 const std::string display_name = "DisplayName";
1047 const std::string settings_url = "url_to_settings";
1048 mojom::ImeInfoPtr info = GenerateImeInfo(android_ime_id, display_name,
1049 settings_url, false, false);
1050 info->ime_id = android_ime_id;
1051 info->display_name = display_name;
1052 info->enabled = false;
1053 info->settings_url = settings_url;
1054
1055 std::vector<mojom::ImeInfoPtr> info_array;
1056 info_array.emplace_back(std::move(info));
1057 service()->OnImeInfoChanged(std::move(info_array));
1058 }
1059 // The proxy IME engine should be added.
1060 ASSERT_EQ(1u, imm()->state()->added_input_method_extensions_.size());
1061 ui::IMEEngineHandlerInterface* engine_handler =
1062 std::get<2>(imm()->state()->added_input_method_extensions_.at(0));
1063
1064 // Set up mock input context.
1065 constexpr int test_context_id = 0;
1066 const ui::IMEEngineHandlerInterface::InputContext test_context{
1067 test_context_id,
1068 ui::TEXT_INPUT_TYPE_TEXT,
1069 ui::TEXT_INPUT_MODE_DEFAULT,
1070 0 /* flags */,
1071 ui::TextInputClient::FOCUS_REASON_MOUSE,
1072 true /* should_do_learning */};
1073 ui::MockInputMethod mock_input_method(nullptr);
1074 TestIMEInputContextHandler test_context_handler(&mock_input_method);
1075 ui::DummyTextInputClient dummy_text_input_client(ui::TEXT_INPUT_TYPE_TEXT);
1076 ui::IMEBridge::Get()->SetInputContextHandler(&test_context_handler);
1077
1078 // Enable the ARC IME.
1079 ui::IMEBridge::Get()->SetCurrentEngineHandler(engine_handler);
1080 engine_handler->Enable(
1081 chromeos::extension_ime_util::GetComponentIDByInputMethodID(
1082 std::get<1>(imm()->state()->added_input_method_extensions_.at(0))
1083 .at(0)
1084 .id()));
1085 mock_input_method.SetFocusedTextInputClient(&dummy_text_input_client);
1086
1087 // Notify non-empty bounds should cause a visibility changed event now.
1088 NotifyNewBounds(gfx::Rect(0, 0, 100, 100));
1089 EXPECT_TRUE(observer.last_visibility());
1090 EXPECT_EQ(1, observer.visibility_changed_call_count());
1091 // A visibility changed event won't be sent if only size is changed.
1092 NotifyNewBounds(gfx::Rect(0, 0, 200, 200));
1093 EXPECT_TRUE(observer.last_visibility());
1094 EXPECT_EQ(1, observer.visibility_changed_call_count());
1095
1096 NotifyNewBounds(gfx::Rect(0, 0, 0, 0));
1097 EXPECT_FALSE(observer.last_visibility());
1098 EXPECT_EQ(2, observer.visibility_changed_call_count());
1099
1100 service()->RemoveObserver(&observer);
1101 }
1102
1103 } // namespace arc
1104